├── .gitignore ├── LICENSE ├── README.md └── src ├── .vscode ├── launch.json └── tasks.json ├── ConsoleApp1 ├── ConsoleApp1.csproj ├── Program.cs ├── Tests │ ├── AllocTest.cs │ ├── AmpCalcs.cs │ ├── ConditionalExpr.cs │ ├── DumpExamples.cs │ ├── ExtendValueTests.cs │ ├── Faker.Names.cs │ ├── Faker.cs │ ├── GZip.cs │ ├── PointerTest.cs │ └── StructTests.cs ├── Tools │ └── StaticUtils.cs └── _Imports.cs ├── LiteDB.Benchmark ├── LiteDB.Benchmark.csproj ├── Program.cs ├── Tests │ └── BsonExpressionTests.cs └── _Imports.cs ├── LiteDB.Generator.Tests ├── LiteDB.Generator.Tests.csproj └── Program.cs ├── LiteDB.Generator.sln ├── LiteDB.Generator ├── Gen │ └── InterfaceGen.cs ├── LiteDB.Generator.csproj ├── LiteGenerator.cs ├── Utils │ ├── AttributeDataExtensions.cs │ ├── Attributes.cs │ ├── CodeBase.cs │ ├── CodeWriter.cs │ ├── CompilerServices.cs │ ├── DEBUG.cs │ ├── StringExtensions.cs │ ├── SymbolExtensions.cs │ ├── SyntaxReceiver.cs │ └── TypeParameterSymbolExtensions.cs └── _Imports.cs ├── LiteDB.Tests ├── Expressions │ ├── BsonExpressions_Execute_Tests.cs │ ├── BsonExpressions_Parser_Tests.cs │ ├── BsonExpressions_ToString_Tests.cs │ └── BsonExpressions_TypeAndIsPredicate_Tests.cs ├── Internals │ └── Engine │ │ ├── CheckpointAction_Tests.cs │ │ ├── Helpers │ │ ├── MockEnumerator.cs │ │ └── MockIndexService.cs │ │ ├── Query │ │ └── Indexes │ │ │ └── IndexEqualsEnumerator_Tests.cs │ │ └── SortService_Tests.cs ├── LiteDB.Tests.csproj ├── Utils │ ├── AssertEx.cs │ └── JsonTokenizer_Tests.cs └── _Imports.cs ├── LiteDB.sln └── LiteDB ├── .editorconfig ├── Document ├── Bson │ ├── BsonReadResult.cs │ ├── BsonReader.cs │ ├── BsonTypeCode.cs │ └── BsonWriter.cs ├── Json │ ├── JsonReader.cs │ ├── JsonReaderStatic.cs │ ├── JsonSerializer.cs │ ├── JsonTokenizer │ │ ├── JsonToken.cs │ │ ├── JsonTokenType.cs │ │ └── JsonTokenizer.cs │ ├── JsonWriter.cs │ └── JsonWriterStatic.cs └── ObjectModel │ ├── BsonArray.cs │ ├── BsonAutoId.cs │ ├── BsonBinary.cs │ ├── BsonBoolean.cs │ ├── BsonDateTime.cs │ ├── BsonDecimal.cs │ ├── BsonDocument.cs │ ├── BsonDouble.cs │ ├── BsonGuid.cs │ ├── BsonInt32.cs │ ├── BsonInt64.cs │ ├── BsonMaxValue.cs │ ├── BsonMinValue.cs │ ├── BsonNull.cs │ ├── BsonObjectId.cs │ ├── BsonString.cs │ ├── BsonType.cs │ ├── BsonValue.cs │ └── ObjectId.cs ├── Engine ├── Commands │ ├── Engine.Execute.cs │ ├── Engine.ExecuteQuery.cs │ ├── Engine.Open.cs │ └── Engine.Shutdown.cs ├── DocumentStore │ ├── DocumentStoreFactory.cs │ ├── FileJson │ │ └── FileJsonDocumentStore.cs │ ├── Interfaces │ │ ├── IDataService.cs │ │ ├── IDocumentStore.cs │ │ └── IIndexService.cs │ ├── SubQuery │ │ └── SubQueryStore.cs │ └── UserCollection │ │ ├── Services │ │ ├── DataService.cs │ │ └── IndexService.cs │ │ └── UserCollectionStore.cs ├── LiteEngine.cs ├── Services │ ├── AllocMapService │ │ └── AllocationMapService.cs │ ├── AutoIdService │ │ └── AutoIdService.cs │ ├── DiskService │ │ ├── DiskService.cs │ │ ├── DiskStream.cs │ │ ├── NewDatafile.cs │ │ ├── StreamFactory │ │ │ ├── FileSortStreamFactory.cs │ │ │ ├── FileStreamFactory.cs │ │ │ ├── IStreamFactory.cs │ │ │ └── MemoryStreamFactory.cs │ │ └── Streams │ │ │ ├── AesStream.cs │ │ │ └── TempStream.cs │ ├── LockService │ │ ├── AsyncReadWriteLock.cs │ │ └── LockService.cs │ ├── LogService │ │ ├── Actions │ │ │ └── CheckpointActions.cs │ │ └── LogService.cs │ ├── MasterService │ │ ├── Mapper │ │ │ └── MasterMapper.cs │ │ ├── MasterService.cs │ │ └── ObjectModel │ │ │ ├── CollectionDocument.cs │ │ │ ├── IndexDocument.cs │ │ │ ├── MasterDocument.cs │ │ │ └── PragmaDocument.cs │ ├── MemoryCache │ │ └── MemoryCache.cs │ ├── MemoryFactory │ │ └── MemoryFactory.cs │ ├── MonitorService │ │ └── MonitorService.cs │ ├── QueryService │ │ ├── AggreagteFunc │ │ │ ├── AnyFunc.cs │ │ │ ├── ArrayFunc.cs │ │ │ ├── AvgFunc.cs │ │ │ ├── CountFunc.cs │ │ │ ├── FirstFunc.cs │ │ │ ├── IAggregateFunc.cs │ │ │ ├── LastFunc.cs │ │ │ ├── MaxFunc.cs │ │ │ ├── MinFunc.cs │ │ │ └── SumFunc.cs │ │ ├── Indexes │ │ │ ├── IndexAllEnumerator.cs │ │ │ ├── IndexEqualsEnumerator.cs │ │ │ ├── IndexInEnumerator.cs │ │ │ ├── IndexLikeEnumerator.cs │ │ │ ├── IndexRangeEnumerator.cs │ │ │ └── IndexScanEnumerator.cs │ │ ├── Lookup │ │ │ ├── DataLookup.cs │ │ │ ├── IDocumentLookup.cs │ │ │ └── IndexLookup.cs │ │ ├── Optimization │ │ │ ├── AggregateOptimization.cs │ │ │ └── QueryOptimization.cs │ │ ├── PipelineBuilder │ │ │ └── PipelineBuilder.cs │ │ ├── PipelineEnumerators │ │ │ ├── AggregateEnumerator.cs │ │ │ ├── FilterEnumerator.cs │ │ │ ├── IPipeEnumerator.cs │ │ │ ├── InMemoryOrderByEnumerator.cs │ │ │ ├── IncludeEnumerator.cs │ │ │ ├── LimitEnumerator.cs │ │ │ ├── LookupEnumerator.cs │ │ │ ├── OffsetEnumerator.cs │ │ │ ├── OrderByEnumerator.cs │ │ │ └── TransformEnumerator.cs │ │ └── QueryService.cs │ ├── RecoveryService │ │ └── RecoveryService.cs │ ├── ServiceFactory │ │ └── ServiceFactory.cs │ ├── SortService │ │ ├── Container │ │ │ ├── SortContainer.cs │ │ │ └── SortOperation.cs │ │ └── SortService.cs │ └── WalIndexService │ │ └── WalIndexService.cs ├── SqlParser │ ├── Commands │ │ ├── SqlParser.CreateCollection.cs │ │ ├── SqlParser.CreateIndex.cs │ │ ├── SqlParser.Insert.cs │ │ └── SqlParser.Select.cs │ ├── Core │ │ ├── SqlParser.AutoId.cs │ │ ├── SqlParser.DocumentStore.cs │ │ ├── SqlParser.ListOfExpressions.cs │ │ ├── SqlParser.Parameters.cs │ │ └── SqlParser.SelectFields.cs │ └── SqlParser.cs ├── Statements │ ├── BsonDataReader │ │ ├── BsonDataReader.cs │ │ ├── BsonDataReaderExtensions.cs │ │ ├── BsonScalarReader.cs │ │ └── IDataReader.cs │ ├── CheckpointStatement.cs │ ├── CreateCollectionStatement.cs │ ├── CreateIndexStatement.cs │ ├── DeleteStatement.cs │ ├── InsertStatement.cs │ ├── Interfaces │ │ ├── EngineStatementType.cs │ │ └── IEngineStatement.cs │ └── SelectStatement.cs ├── Structures │ ├── Core │ │ ├── EngineSettings.cs │ │ ├── FileHeader.cs │ │ ├── RowID.cs │ │ └── Sequence.cs │ ├── Enums │ │ ├── CheckpointActionType.cs │ │ ├── EngineState.cs │ │ └── PageType.cs │ ├── Extend │ │ ├── ExtendLocation.cs │ │ └── ExtendPageValue.cs │ ├── Log │ │ ├── CheckpointAction.cs │ │ ├── LogPageHeader.cs │ │ └── LogPosition.cs │ ├── Query │ │ ├── Cursor.cs │ │ ├── ExplainPlainBuilder.cs │ │ ├── Into.cs │ │ ├── OrderBy.cs │ │ ├── PipeContext.cs │ │ ├── PipeEmit.cs │ │ ├── PipeValue.cs │ │ ├── Query.cs │ │ ├── Resultset.cs │ │ ├── SelectField.cs │ │ └── SelectFields.cs │ └── Sort │ │ ├── SortItem.cs │ │ └── SortItemDocument.cs ├── Transaction │ ├── Transaction.Abort.cs │ ├── Transaction.Commit.cs │ ├── Transaction.GetPage.cs │ ├── Transaction.Safepoint.cs │ └── Transaction.cs └── Unsafe │ ├── Enumerator │ └── IndexNodeEnumerator.cs │ ├── IndexKey │ ├── IndexKey.Compare.cs │ ├── IndexKey.GetSize.cs │ ├── IndexKey.Initialize.cs │ ├── IndexKey.ToBsonValue.cs │ └── IndexKey.cs │ ├── PageMemory │ ├── PageMemory.AllocMap.cs │ ├── PageMemory.DataBlock.cs │ ├── PageMemory.IndexNode.cs │ ├── PageMemory.Segment.cs │ └── PageMemory.cs │ ├── Results │ ├── DataBlockResult.cs │ └── IndexNodeResult.cs │ ├── Sort │ ├── SortContainer2.cs │ ├── SortItem2.cs │ └── SortPage2.cs │ └── Structs │ ├── DataBlock.cs │ ├── IndexNode.cs │ ├── IndexNodeLevel.cs │ └── PageSegment.cs ├── Expression ├── Attributes │ └── VolatileAttribute.cs ├── BsonExpression.Static.cs ├── BsonExpression.Static.cs.bak ├── BsonExpression.cs ├── BsonExpressionContext.cs ├── BsonExpressionInfo.cs ├── BsonExpressionParser.cs ├── BsonExpressionParser.cs.bak ├── BsonExpressionType.cs ├── ExpressionMethods │ ├── Aggregate.cs │ ├── DataTypes.cs │ ├── Date.cs │ ├── Math.cs │ ├── Misc.cs │ └── String.cs ├── InternalExpressions │ ├── ArrayIndexBsonExpression.cs │ ├── BinaryBsonExpression.cs │ ├── CallBsonExpression.cs │ ├── ConditionalBsonExpression.cs │ ├── ConstantBsonExpression.cs │ ├── EmptyBsonExpression.cs │ ├── FilterBsonExpression.cs │ ├── InnerBsonExpression.cs │ ├── MakeArrayBsonExpression.cs │ ├── MakeDocumentBsonExpression.cs │ ├── MapBsonExpression.cs │ ├── ParameterBsonExpression.cs │ ├── PathBsonExpression.cs │ └── ScopeBsonExpression.cs └── Tokenizer │ ├── Token.cs │ ├── Token.cs.bak │ ├── TokenType.cs │ ├── Tokenizer.cs │ └── Tokenizer.cs.bak ├── LiteDB.csproj ├── Utils ├── Collation.cs ├── CompilerServices.cs ├── Constants.cs ├── Crc8.cs ├── Debugger │ ├── CleanerExtensions.cs │ ├── CodeContract.cs │ ├── CodeContractExpression.cs │ ├── DumpExtensions.cs │ ├── PageDump.cs │ └── Profiler.cs ├── ExceptionExtensions.cs ├── Exceptions │ ├── LiteException.Errors.cs │ └── LiteException.cs ├── Extensions │ ├── DateExtensions.cs │ ├── DictionaryExtensions.cs │ ├── HashSetExtensions.cs │ ├── IOExceptionExtensions.cs │ ├── LinqExtensions.cs │ ├── MarshalExtensions.cs │ ├── SpanExtensions.cs │ └── StringExtensions.cs ├── FileHelper.cs ├── Interfaces │ └── IIsEmpty.cs ├── Randomizer.cs ├── Reflection.Expression.cs ├── Reflection.cs ├── SharedArray.cs └── StringBuilderCache.cs ├── _Imports.cs └── icon_64x64.png /LICENSE: -------------------------------------------------------------------------------- 1 | LiteDB vNext -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiteDB vNext 2 | 3 | LiteDB-vNew is now in the main repository (`dev` branch) - https://github.com/mbdavid/LiteDB/tree/dev 4 | -------------------------------------------------------------------------------- /src/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/ConsoleApp1/bin/Debug/net6.0/ConsoleApp1.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/ConsoleApp1", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /src/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/ConsoleApp1/ConsoleApp1.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/ConsoleApp1/ConsoleApp1.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/ConsoleApp1/ConsoleApp1.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/ConsoleApp1/ConsoleApp1.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | True 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/AllocTest.cs: -------------------------------------------------------------------------------- 1 | //using System.Runtime.InteropServices; 2 | 3 | //using static LiteDB.Constants; 4 | 5 | 6 | //var start = GC.GetAllocatedBytesForCurrentThread(); 7 | 8 | //Console.WriteLine(start); 9 | 10 | //var m = Marshal.AllocHGlobal(819200); 11 | 12 | ////await Task.Delay(1000); 13 | 14 | //var end = GC.GetAllocatedBytesForCurrentThread(); 15 | 16 | //var diff = end - start; 17 | 18 | //Console.WriteLine(end); 19 | //Console.WriteLine($"Total Allocated: {diff}"); 20 | 21 | //Marshal.FreeHGlobal(m); -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/AmpCalcs.cs: -------------------------------------------------------------------------------- 1 | //using static LiteDB.Constants; 2 | 3 | 4 | //var tests = new uint[] { 1, 2, 8, 9, 16, 17, 16319, 16320, 16321, 16322, 32641, 32642, 32643, 32650, 32651, 16333, 32656 }; 5 | 6 | //Console.WriteLine("AM_EXTEND_COUNT: {0}", AM_EXTEND_COUNT); 7 | //Console.WriteLine("AM_PAGE_STEP: {0}", AM_PAGE_STEP); 8 | //Console.WriteLine("AM_MAP_PAGES_COUNT: {0}", AM_MAP_PAGES_COUNT); 9 | 10 | //foreach (var test in tests) 11 | //{ 12 | // var page = new 13 | // { 14 | // PageID = test 15 | // }; 16 | 17 | // var allocationMapID = (int)(page.PageID / AM_PAGE_STEP); 18 | // var extendIndex = (page.PageID - 1 - allocationMapID * AM_PAGE_STEP) / AM_EXTEND_SIZE; 19 | // var pageIndex = page.PageID - 1 - allocationMapID * AM_PAGE_STEP - extendIndex * AM_EXTEND_SIZE; 20 | 21 | // Console.WriteLine("{0}: {1}, {2}, {3}", page.PageID, allocationMapID, extendIndex, pageIndex); 22 | // Console.WriteLine("{0}: {1}", page.PageID, GetBlockPageID(allocationMapID, extendIndex, pageIndex)); 23 | //} 24 | 25 | //uint GetBlockPageID(int allocationMapID, long extendIndex, long pageIndex) 26 | //{ 27 | // return (uint) 28 | // (allocationMapID * AM_PAGE_STEP + 29 | // extendIndex * AM_EXTEND_SIZE + 30 | // pageIndex + 1); 31 | //} 32 | 33 | -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/ConditionalExpr.cs: -------------------------------------------------------------------------------- 1 | //var a = true; 2 | //var x = false; 3 | //var n = 10; 4 | 5 | //var rr = a == true ? x == a ? 'r' : a == false ? 'f' : a == false && a == true ? 'x' + 'y' : 'z' : 'd'; 6 | 7 | //var ss = new string[] 8 | //{ 9 | // "a = true ? 's' : 'f'", 10 | // "a = true ? 't' : a = false ? 'f' : 'xx'", 11 | // "a = true ? a != false ? 'vv' : 'f' : 'ff'", 12 | // "a = true ? 's' : a = false ? 'f' : 'nenhum'", 13 | // "$.a=true?$.x=$.a?'r':$.a=false?'f':$.a=false AND $.a=true?'x'+'y':'z':'d'", 14 | // "(a = true) ? (x = a) ? 'r' : (a = false) ? 'f' : (a = false and a = true) ? 'x' + 'y' : 'z' : 'd'", 15 | // "(a = true) ? (x = a) ? (x = a) ? 'r' : (a = false) ? 'f' : (a = false and a = true) ? 'x' + 'y' : 'z' : 'd' : 'p'", 16 | // "n = 1 ? '1' : n = 2 ? '2' : n = 3 ? '3' : n = 4 ? '4' : n = 5 ? '5' : n = 6 ? '6' : 'outro'", 17 | // "'teste' + a = true ? 's' : 'f'", 18 | // "'teste' + (a = true ? 's' : 'f')", 19 | //}; 20 | 21 | 22 | ////Console.WriteLine(x.ToString()); 23 | 24 | //foreach (var s in ss) 25 | //{ 26 | // var doc = new BsonDocument { ["a"] = true }; 27 | // var exp = BsonExpression.Create(s); 28 | 29 | // Console.WriteLine("Str : " + s); 30 | // Console.WriteLine("Expr: " + exp.ToString()); 31 | // Console.WriteLine("Exec: " + exp.Execute(doc).ToString()); 32 | // Console.WriteLine("----------------------------------------------"); 33 | //} 34 | 35 | 36 | //Console.ReadKey(); 37 | 38 | //return; -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/DumpExamples.cs: -------------------------------------------------------------------------------- 1 | //using LiteDB; 2 | //using LiteDB.Engine; 3 | 4 | //var _demo = "test"; 5 | //var tests = new uint[] { 1, 2 }; 6 | //var sortItem = new SortItem(new RowID(8,3), new BsonDocument { ["ola"] = 10 }); 7 | 8 | //var dump = Dump.Object(new { _demo, nome = tests, sortItem }); 9 | 10 | 11 | //Console.WriteLine(dump); -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/ExtendValueTests.cs: -------------------------------------------------------------------------------- 1 | //using LiteDB; 2 | //using LiteDB.Engine; 3 | 4 | //var extendValues = new uint[] 5 | //{ 6 | // 0b01100010_111_111_111_111_111_111_111_111, 7 | // 0b01100010_000_000_000_000_000_000_000_000, 8 | 9 | // 0b01100010_111_111_111_111_111_111_111_000, 10 | // 0b01100010_000_000_000_000_000_000_000_001, 11 | // 0b01100010_000_000_000_000_000_000_000_010, 12 | 13 | // 0b01100010_000_111_111_111_111_111_111_111, 14 | // 0b01100010_111_000_111_111_111_111_111_111, 15 | // 0b01100010_111_111_000_111_111_111_111_111, 16 | // 0b01100010_111_111_111_000_111_111_111_111, 17 | // 0b01100010_111_111_111_111_000_111_111_111, 18 | // 0b01100010_111_111_111_111_111_000_111_111, 19 | // 0b01100010_111_111_111_111_111_111_000_111, 20 | // 0b01100010_111_111_111_111_111_111_111_000, 21 | 22 | // 0b01100010_001_111_111_111_111_111_111_111, 23 | 24 | //}; 25 | 26 | //byte colID = 98; 27 | //var pageType = PageType.Data; 28 | 29 | //foreach (var value in extendValues) 30 | //{ 31 | // Print(value); 32 | //} 33 | 34 | 35 | //void Print(uint value) 36 | //{ 37 | // var result = AllocationMapPage.HasFreeSpaceInExtend(value, colID, pageType); 38 | 39 | // Console.WriteLine(Dump.ExtendValue(value) + " => " + result); 40 | //} 41 | 42 | -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/Faker.cs: -------------------------------------------------------------------------------- 1 | internal static partial class Faker 2 | { 3 | private static Random _random = new Random(420); 4 | 5 | 6 | public static string Fullname() 7 | { 8 | var names = _random.NextBool() ? _maleNames : _femaleNames; 9 | 10 | return names[_random.Next(names.Length - 1)] + " " + _surNames[_random.Next(_surNames.Length - 1)]; 11 | } 12 | 13 | public static int Age() 14 | { 15 | return _random.Next(18, 96); 16 | } 17 | 18 | public static DateTime Birthday() 19 | { 20 | var oldest = DateTime.Today.AddYears(-110).Ticks; 21 | var now = DateTime.Now.Ticks; 22 | var range = now - oldest; 23 | 24 | var date = new DateTime(oldest + _random.NextLong(0, range)); 25 | 26 | return date; 27 | } 28 | 29 | public static string Lorem(int size, int end = -1) 30 | { 31 | return string.Join(" ", Enumerable.Range(1, end == -1 ? size : _random.Next(size, end)) 32 | .Select(x => _lorem[_random.Next(_lorem.Length - 1)])); 33 | } 34 | 35 | public static int Next(int start, int end) 36 | { 37 | return _random.Next(start, end); 38 | } 39 | 40 | // https://stackoverflow.com/a/13095144/3286260 41 | public static long NextLong(this Random random, long min, long max) 42 | { 43 | if (max <= min) 44 | throw new ArgumentOutOfRangeException("max", "max must be > min!"); 45 | 46 | //Working with ulong so that modulo works correctly with values > long.MaxValue 47 | ulong uRange = (ulong)(max - min); 48 | 49 | //Prevent a modolo bias; see https://stackoverflow.com/a/10984975/238419 50 | //for more information. 51 | //In the worst case, the expected number of calls is 2 (though usually it's 52 | //much closer to 1) so this loop doesn't really hurt performance at all. 53 | ulong ulongRand; 54 | do 55 | { 56 | byte[] buf = new byte[8]; 57 | random.NextBytes(buf); 58 | ulongRand = (ulong)BitConverter.ToInt64(buf, 0); 59 | } while (ulongRand > ulong.MaxValue - ((ulong.MaxValue % uRange) + 1) % uRange); 60 | 61 | return (long)(ulongRand % uRange) + min; 62 | } 63 | 64 | public static bool NextBool(this Random random) 65 | { 66 | return random.NextSingle() >= 0.5; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/GZip.cs: -------------------------------------------------------------------------------- 1 | internal class GZip 2 | { 3 | public static void Run() 4 | { 5 | 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ConsoleApp1/Tests/StructTests.cs: -------------------------------------------------------------------------------- 1 | //public ref struct Token 2 | //{ 3 | // public ReadOnlySpan Value; 4 | 5 | // public Token(ReadOnlySpan value) 6 | // { 7 | // this.Value = value; 8 | // } 9 | //} 10 | 11 | 12 | //internal class Tokenizer 13 | //{ 14 | // public Tokenizer(string source) 15 | // { 16 | 17 | // } 18 | 19 | // // nao anda 20 | // public Token GetCurrent(bool eatWhitespace) 21 | // { 22 | 23 | // } 24 | 25 | // // anda 26 | // public Token GetNext(bool eatWhitespace) 27 | // { 28 | // } 29 | 30 | // // nao anda 31 | // public Token LookAhead(bool eatWhitespace, int count) 32 | // { 33 | // } 34 | 35 | //} 36 | -------------------------------------------------------------------------------- /src/ConsoleApp1/_Imports.cs: -------------------------------------------------------------------------------- 1 | global using LiteDB; 2 | global using LiteDB.Engine; 3 | global using static LiteDB.Constants; 4 | global using static LiteDB.BsonExpression; 5 | global using System.Diagnostics; 6 | global using System.Runtime.InteropServices; -------------------------------------------------------------------------------- /src/LiteDB.Benchmark/LiteDB.Benchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/LiteDB.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | BenchmarkRunner.Run(); 2 | -------------------------------------------------------------------------------- /src/LiteDB.Benchmark/Tests/BsonExpressionTests.cs: -------------------------------------------------------------------------------- 1 | [RPlotExporter] 2 | [MemoryDiagnoser] 3 | public class BsonExpressionTests 4 | { 5 | private BsonDocument _doc = new() { ["a"] = 34m }; 6 | 7 | [Benchmark] 8 | public void ExprExec() => BsonExpression.Create("(45 + 12 * a) > 99").Execute(_doc); 9 | 10 | [Benchmark] 11 | public void ExprParseCompileExec() => BsonExpressionParser.ParseFullExpression(new Tokenizer("(45 + 12 * a) > 99"), true).Execute(_doc); 12 | } 13 | -------------------------------------------------------------------------------- /src/LiteDB.Benchmark/_Imports.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Globalization; 3 | global using System.Collections; 4 | global using System.Collections.Generic; 5 | global using System.Collections.Concurrent; 6 | global using System.Text; 7 | global using System.Threading; 8 | global using System.Threading.Tasks; 9 | global using System.Linq; 10 | global using System.IO; 11 | global using System.Text.RegularExpressions; 12 | global using System.Runtime.InteropServices; 13 | global using System.Runtime.CompilerServices; 14 | global using System.Security; 15 | global using System.Diagnostics; 16 | global using System.Security.Cryptography; 17 | global using System.Reflection; 18 | global using System.Buffers; 19 | global using System.Buffers.Binary; 20 | 21 | global using BenchmarkDotNet.Attributes; 22 | global using BenchmarkDotNet.Running; 23 | 24 | global using LiteDB; 25 | global using LiteDB.Engine; 26 | global using static LiteDB.Constants; 27 | global using static LiteDB.LiteException; 28 | 29 | -------------------------------------------------------------------------------- /src/LiteDB.Generator.Tests/LiteDB.Generator.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | True 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/LiteDB.Generator.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | public class Program 4 | { 5 | public static void Main(string[] args) 6 | { 7 | 8 | } 9 | } 10 | 11 | 12 | 13 | [AutoInterface(typeof(IDisposable))] 14 | public unsafe class DiskService : IDiskService 15 | { 16 | public DiskService() 17 | { 18 | } 19 | 20 | public void Dispose() 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | public Stream RendStreamReader() 26 | { 27 | throw new NotImplementedException(); 28 | } 29 | 30 | public void ReturnReader(Stream stream) 31 | { 32 | } 33 | } 34 | 35 | [AutoInterface] 36 | public class StreamPool : IStreamPool 37 | { 38 | public StreamPool(int limit) 39 | { 40 | } 41 | 42 | public Stream RendStreamReader() 43 | { 44 | return new MemoryStream(); 45 | } 46 | 47 | public void ReturnReader(Stream stream) 48 | { 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33213.308 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteDB.Generator", "LiteDB.Generator\LiteDB.Generator.csproj", "{450640F8-A2BF-4DED-A3B8-232D588E8ACA}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiteDB.Generator.Tests", "LiteDB.Generator.Tests\LiteDB.Generator.Tests.csproj", "{948EA78B-0083-499D-BA3F-AAE95A585207}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {450640F8-A2BF-4DED-A3B8-232D588E8ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {450640F8-A2BF-4DED-A3B8-232D588E8ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {450640F8-A2BF-4DED-A3B8-232D588E8ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {450640F8-A2BF-4DED-A3B8-232D588E8ACA}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {948EA78B-0083-499D-BA3F-AAE95A585207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {948EA78B-0083-499D-BA3F-AAE95A585207}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {948EA78B-0083-499D-BA3F-AAE95A585207}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {948EA78B-0083-499D-BA3F-AAE95A585207}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1AE6FEF2-9F53-4CC5-A9EC-ECC923DED05D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/LiteDB.Generator/LiteDB.Generator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2 5 | 11.0 6 | enable 7 | true 8 | false 9 | false 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/LiteDB.Generator/LiteGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom.Compiler; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading; 10 | using Microsoft.CodeAnalysis; 11 | using Microsoft.CodeAnalysis.CSharp; 12 | using Microsoft.CodeAnalysis.Text; 13 | 14 | namespace LiteDB.Generator; 15 | 16 | [Generator] 17 | public class LiteGenerator : ISourceGenerator 18 | { 19 | public void Initialize(GeneratorInitializationContext context) 20 | { 21 | context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); 22 | } 23 | 24 | public void Execute(GeneratorExecutionContext context) 25 | { 26 | try 27 | { 28 | ExecuteCore(context); 29 | } 30 | catch (Exception exception) 31 | { 32 | RaiseExceptionDiagnostic(context, exception); 33 | } 34 | } 35 | 36 | private void ExecuteCore(GeneratorExecutionContext context) 37 | { 38 | // adding AutoInterfaceAttribute to source code 39 | context.AddSource( 40 | Attributes.AutoInterfaceClassname, 41 | SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8)); 42 | 43 | var codeBase = new CodeBase(context); 44 | 45 | InterfaceGen.GenerateCode(codeBase); 46 | } 47 | 48 | private static void RaiseExceptionDiagnostic(GeneratorExecutionContext context, Exception exception) 49 | { 50 | var descriptor = new DiagnosticDescriptor( 51 | "InterfaceGenerator.CriticalError", 52 | $"Exception thrown in InterfaceGenerator", 53 | $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}", 54 | "InterfaceGenerator", 55 | DiagnosticSeverity.Error, 56 | true, 57 | customTags: WellKnownDiagnosticTags.AnalyzerException); 58 | 59 | var diagnostic = Diagnostic.Create(descriptor, null); 60 | 61 | context.ReportDiagnostic(diagnostic); 62 | } 63 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/AttributeDataExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Generator; 2 | 3 | internal static class AttributeDataExtensions 4 | { 5 | public static string? GetNamedParamValue(this AttributeData attributeData, string paramName) 6 | { 7 | var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName); 8 | return pair.Value.Value?.ToString(); 9 | } 10 | 11 | public static string? GetConstructorValue(this AttributeData attributeData) 12 | { 13 | var value = attributeData.ConstructorArguments.FirstOrDefault(); 14 | return value.Value?.ToString(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/Attributes.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Generator; 2 | 3 | internal class Attributes 4 | { 5 | public const string AttributesNamespace = "LiteDB"; 6 | 7 | public const string AutoInterfaceClassname = "AutoInterfaceAttribute"; 8 | 9 | public static readonly string AttributesSourceCode = $@" 10 | 11 | using System; 12 | using System.Diagnostics; 13 | 14 | #nullable enable 15 | 16 | namespace {AttributesNamespace} 17 | {{ 18 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] 19 | [Conditional(""CodeGeneration"")] 20 | internal sealed class {AutoInterfaceClassname} : Attribute 21 | {{ 22 | public {AutoInterfaceClassname}() 23 | {{ 24 | }} 25 | 26 | public {AutoInterfaceClassname}(Type inheritInterface) 27 | {{ 28 | }} 29 | }} 30 | }} 31 | "; 32 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices; 2 | 3 | /// 4 | /// Class definition for works with "init;" keyword in .netstandard project 5 | /// 6 | public class IsExternalInit 7 | { 8 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/DEBUG.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | public static class SourceGenDebugger 4 | { 5 | [Conditional("DEBUG")] 6 | public static void DEBUG() 7 | { 8 | #if DEBUG 9 | if (!Debugger.IsAttached) 10 | { 11 | // sadly this is Windows only so as of now :( 12 | //Debugger.Launch(); 13 | } 14 | #endif 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/SyntaxReceiver.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Generator; 2 | 3 | internal class SyntaxReceiver : ISyntaxReceiver 4 | { 5 | public IList CandidateTypes { get; } = new List(); 6 | 7 | public void OnVisitSyntaxNode(SyntaxNode syntaxNode) 8 | { 9 | if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax && 10 | IsClassOrRecord(typeDeclarationSyntax) && 11 | typeDeclarationSyntax.AttributeLists.Count > 0) 12 | { 13 | this.CandidateTypes.Add(typeDeclarationSyntax); 14 | } 15 | } 16 | 17 | private static bool IsClassOrRecord(TypeDeclarationSyntax typeDeclarationSyntax) 18 | { 19 | return typeDeclarationSyntax is ClassDeclarationSyntax || typeDeclarationSyntax is RecordDeclarationSyntax; 20 | } 21 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/Utils/TypeParameterSymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Generator; 2 | 3 | internal static class TypeParameterSymbolExtensions 4 | { 5 | public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol) 6 | { 7 | // the class/struct/unmanaged/notnull constraint has to be the last 8 | if (symbol.HasNotNullConstraint) 9 | { 10 | yield return "notnull"; 11 | } 12 | 13 | if (symbol.HasValueTypeConstraint) 14 | { 15 | yield return "struct"; 16 | } 17 | 18 | if (symbol.HasUnmanagedTypeConstraint) 19 | { 20 | yield return "unmanaged"; 21 | } 22 | 23 | if (symbol.HasReferenceTypeConstraint) 24 | { 25 | yield return symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated 26 | ? "class?" 27 | : "class"; 28 | } 29 | 30 | // types go in the middle 31 | foreach (var constraintType in symbol.ConstraintTypes) 32 | { 33 | yield return constraintType.ToDisplayString(); 34 | } 35 | 36 | // the new() constraint has to be the last 37 | if (symbol.HasConstructorConstraint) 38 | { 39 | yield return "new()"; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/LiteDB.Generator/_Imports.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Linq; 3 | global using System.Text; 4 | global using System.Collections.Generic; 5 | global using System.IO; 6 | 7 | global using Microsoft.CodeAnalysis; 8 | global using Microsoft.CodeAnalysis.CSharp; 9 | global using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | global using Microsoft.CodeAnalysis.Text; 11 | 12 | global using static SourceGenDebugger; 13 | -------------------------------------------------------------------------------- /src/LiteDB.Tests/Expressions/BsonExpressions_TypeAndIsPredicate_Tests.cs: -------------------------------------------------------------------------------- 1 | using static LiteDB.BsonExpression; 2 | 3 | namespace LiteDB.Tests.Expressions; 4 | 5 | public class BsonExpressions_TypeAndIsPredicate_Tests 6 | { 7 | [Theory] 8 | [InlineData("21", BsonExpressionType.Constant, false)] 9 | [InlineData("2.6", BsonExpressionType.Constant, false)] 10 | [InlineData("'string'", BsonExpressionType.Constant, false)] 11 | [InlineData("2+1", BsonExpressionType.Add, false)] 12 | [InlineData("2-1", BsonExpressionType.Subtract, false)] 13 | [InlineData("2*1", BsonExpressionType.Multiply, false)] 14 | [InlineData("2/1", BsonExpressionType.Divide, false)] 15 | [InlineData("[1,2,3]", BsonExpressionType.Array, false)] 16 | [InlineData("1=1", BsonExpressionType.Equal, true)] 17 | [InlineData("2!=1", BsonExpressionType.NotEqual, true)] 18 | [InlineData("2>1", BsonExpressionType.GreaterThan, true)] 19 | [InlineData("2>=1", BsonExpressionType.GreaterThanOrEqual, true)] 20 | [InlineData("1<2", BsonExpressionType.LessThan, true)] 21 | [InlineData("1<=2", BsonExpressionType.LessThanOrEqual, true)] 22 | [InlineData("@p0", BsonExpressionType.Parameter, false)] 23 | [InlineData("UPPER(@p0)", BsonExpressionType.Call, false)] 24 | [InlineData("'LiteDB' LIKE 'L%'", BsonExpressionType.Like, true)] 25 | [InlineData("7 BETWEEN 4 AND 10", BsonExpressionType.Between, true)] 26 | [InlineData("7 IN [1,4,7]", BsonExpressionType.In, true)] 27 | [InlineData("true AND true", BsonExpressionType.And, false)] 28 | [InlineData("true OR false", BsonExpressionType.Or, false)] 29 | [InlineData("arr=>@", BsonExpressionType.Map, false)] 30 | public void BsonExpressionTypeANDIsPredicate_Theory(string exp, BsonExpressionType type, bool isPredicate) 31 | { 32 | var expr = Create(exp); 33 | expr.Type.Should().Be(type); 34 | expr.IsPredicate.Should().Be(isPredicate); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/LiteDB.Tests/Internals/Engine/Helpers/MockEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Tests.Internals.Engine; 2 | 3 | internal class MockEnumerator : IPipeEnumerator 4 | { 5 | private readonly Queue _items; 6 | 7 | public MockEnumerator(IEnumerable values) 8 | { 9 | _items = new Queue(values); 10 | } 11 | 12 | public PipeEmit Emit => new(true, true, true); 13 | 14 | public PipeValue MoveNext(PipeContext context) 15 | { 16 | if (_items.Count == 0) return PipeValue.Empty; 17 | 18 | var item = _items.Dequeue(); 19 | 20 | return item; 21 | } 22 | 23 | public void GetPlan(ExplainPlainBuilder builder, int deep) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | public void Dispose() 29 | { 30 | } 31 | } -------------------------------------------------------------------------------- /src/LiteDB.Tests/Internals/Engine/Query/Indexes/IndexEqualsEnumerator_Tests.cs: -------------------------------------------------------------------------------- 1 | //using NSubstitute; 2 | //using System.Threading.Tasks; 3 | 4 | //namespace LiteDB.Tests.Internals.Engine; 5 | 6 | //public class IndexEqualsEnumerator_Tests 7 | //{ 8 | // private readonly IndexDocument _doc = Substitute.For(); 9 | // private readonly IIndexService _indexService = new MockIndexService(); 10 | 11 | // [Fact] 12 | // public void MoveNextAsync() 13 | // { 14 | // #region Arrange 15 | // var indexDocument = new IndexDocument() 16 | // { 17 | // Slot = 0, 18 | // Name = "_id", 19 | // Expression = "$._id", 20 | // Unique = true, 21 | // HeadIndexNodeID = new RowID(0, 1), 22 | // TailIndexNodeID = new RowID(10, 1) 23 | 24 | // }; 25 | // var _sut = new IndexEqualsEnumerator(1, indexDocument, Collation.Default); 26 | 27 | // var pipeContext = new PipeContext(null, _indexService, new BsonDocument()); 28 | 29 | // #endregion 30 | 31 | 32 | // #region Act 33 | // var value = _sut.MoveNextAsync(pipeContext); 34 | // #endregion 35 | 36 | 37 | // #region Asserts 38 | // value.Should().Be(new RowID(0, 0)); 39 | // #endregion 40 | // } 41 | //} -------------------------------------------------------------------------------- /src/LiteDB.Tests/LiteDB.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7 5 | LiteDB.Tests 6 | LiteDB.Tests 7 | Maurício David 8 | MIT 9 | en-US 10 | false 11 | 1701;1702;1705;1591;0618 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/LiteDB.Tests/_Imports.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Globalization; 3 | global using System.Collections; 4 | global using System.Collections.Generic; 5 | global using System.Collections.Concurrent; 6 | global using System.Text; 7 | global using System.Threading; 8 | global using System.Threading.Tasks; 9 | global using System.Linq; 10 | global using System.IO; 11 | global using System.Text.RegularExpressions; 12 | global using System.Runtime.InteropServices; 13 | global using System.Runtime.CompilerServices; 14 | global using System.Security; 15 | global using System.Diagnostics; 16 | global using System.Security.Cryptography; 17 | global using System.Reflection; 18 | global using System.Buffers; 19 | global using System.Buffers.Binary; 20 | 21 | global using LiteDB; 22 | global using LiteDB.Engine; 23 | global using static LiteDB.Constants; 24 | global using static LiteDB.LiteException; 25 | 26 | global using Xunit; 27 | global using NSubstitute; 28 | global using FluentAssertions; 29 | global using Bogus; -------------------------------------------------------------------------------- /src/LiteDB/.editorconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbdavid/LiteDB-vNext/2941736fcf4e8efb23e8c8228a6d6532e361702a/src/LiteDB/.editorconfig -------------------------------------------------------------------------------- /src/LiteDB/Document/Bson/BsonReadResult.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public readonly struct BsonReadResult : IIsEmpty 4 | { 5 | public static readonly BsonReadResult Empty = new(); 6 | 7 | private readonly BsonValue? _value; 8 | private readonly Exception? _exception; 9 | 10 | public BsonValue Value => _value!; 11 | public Exception Exception => _exception!; 12 | 13 | public bool Ok => _exception is null; 14 | public bool Fail => _exception is not null; 15 | 16 | public bool IsEmpty => _value is null; 17 | 18 | public BsonReadResult() 19 | { 20 | _value = null; 21 | _exception = null; 22 | } 23 | 24 | public BsonReadResult(BsonValue value) 25 | { 26 | _value = value; 27 | _exception = null; 28 | } 29 | 30 | public BsonReadResult(Exception ex) 31 | { 32 | _value = default; 33 | _exception = ex; 34 | } 35 | 36 | public BsonReadResult(BsonValue value, Exception ex) 37 | { 38 | _value = value; 39 | _exception = ex; 40 | } 41 | 42 | public static implicit operator BsonReadResult(BsonValue value) => new (value); 43 | 44 | public static implicit operator BsonReadResult(Exception ex) => new (ex); 45 | 46 | public override string ToString() 47 | { 48 | return IsEmpty ? "" : Dump.Object(new { Ok, Value = Value.ToString() }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/LiteDB/Document/Bson/BsonTypeCode.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent BSON data type in disk only (used when serialize/deserialize). 5 | /// Do not change values in current FILE_VERSION (can be added) 6 | /// Must use 5 bits only (3 bis are reserved) 7 | /// 8 | public enum BsonTypeCode : byte 9 | { 10 | MinValue = 0, 11 | 12 | Null = 1, 13 | 14 | Int32 = 2, 15 | Int64 = 3, 16 | Double = 4, 17 | Decimal = 5, 18 | 19 | String = 6, 20 | 21 | Document = 7, 22 | Array = 8, 23 | 24 | Binary = 9, 25 | ObjectId = 10, 26 | Guid = 11, 27 | DateTime = 12, 28 | // reserved for DateTimeOffset 29 | False = 20, // Boolean False 30 | True = 21, // Boolean True 31 | 32 | MaxValue = 31, // use 5 bits max (keep 3 for extends) 33 | 34 | } -------------------------------------------------------------------------------- /src/LiteDB/Document/Json/JsonTokenizer/JsonToken.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a single string token 5 | /// 6 | internal struct JsonToken 7 | { 8 | public JsonToken(JsonTokenType tokenType, string value, long position) 9 | { 10 | this.Position = position; 11 | this.Value = value; 12 | this.Type = tokenType; 13 | } 14 | 15 | public JsonTokenType Type { get; private set; } 16 | public string Value { get; private set; } 17 | public long Position { get; private set; } 18 | 19 | /// 20 | /// Expect for one of the types (if not, throw UnexpectedToken) 21 | /// 22 | public JsonToken Expect(params JsonTokenType[] types) 23 | { 24 | foreach(var type in types) 25 | { 26 | if (this.Type == type) 27 | { 28 | return this; 29 | } 30 | } 31 | throw new Exception(); 32 | } 33 | 34 | public bool Is(string value, bool ignoreCase = true) 35 | { 36 | return 37 | this.Type == JsonTokenType.Word && 38 | value.Equals(this.Value, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return $"{Value} ({Type})"; 44 | } 45 | } -------------------------------------------------------------------------------- /src/LiteDB/Document/Json/JsonTokenizer/JsonTokenType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal enum JsonTokenType 4 | { 5 | /// { 6 | OpenBrace, 7 | /// } 8 | CloseBrace, 9 | /// [ 10 | OpenBracket, 11 | /// ] 12 | CloseBracket, 13 | /// , 14 | Comma, 15 | /// : 16 | Colon, 17 | /// ; 18 | SemiColon, 19 | /// . 20 | Period, 21 | /// - 22 | Minus, 23 | /// + 24 | Plus, 25 | /// * 26 | Asterisk, 27 | /// / 28 | Slash, 29 | /// \ 30 | Backslash, 31 | /// "..." or '...' 32 | String, 33 | /// [0-9]+ 34 | Int, 35 | /// [0-9]+.[0-9] 36 | Double, 37 | /// \n\r\t \u0032 38 | Whitespace, 39 | /// [a-Z_$]+[a-Z0-9_$] 40 | Word, 41 | EOF, 42 | Unknown 43 | } -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonAutoId.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// All supported BsonTypes supported in AutoId insert operation 5 | /// 6 | public enum BsonAutoId : byte 7 | { 8 | Int32 = 2, 9 | Int64 = 3, 10 | ObjectId = 10, 11 | Guid = 11 12 | } -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonBinary.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a Binary value (byte array) in Bson object model 5 | /// 6 | public class BsonBinary : BsonValue 7 | { 8 | public byte[] Value { get; } 9 | 10 | public BsonBinary(byte[] value) 11 | { 12 | this.Value = value ?? throw new ArgumentNullException(nameof(value)); 13 | } 14 | 15 | public override BsonType Type => BsonType.Binary; 16 | 17 | public override int GetBytesCount() => this.Value.Length; 18 | 19 | public override int GetHashCode() => this.Value.GetHashCode(); 20 | 21 | #region Implement CompareTo 22 | 23 | public override int CompareTo(BsonValue other, Collation collation) 24 | { 25 | if (other is BsonBinary otherBinary) return this.Value.AsSpan().SequenceCompareTo(otherBinary.Value); 26 | 27 | return this.CompareType(other); 28 | } 29 | 30 | #endregion 31 | } 32 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonBoolean.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a Boolean value in Bson object model 5 | /// 6 | internal class BsonBoolean : BsonValue 7 | { 8 | public static readonly BsonBoolean True = new (true); 9 | public static readonly BsonBoolean False = new (false); 10 | 11 | public bool Value { get; } 12 | 13 | public BsonBoolean(bool value) 14 | { 15 | this.Value = value; 16 | } 17 | 18 | public override BsonType Type => BsonType.Boolean; 19 | 20 | public override int GetBytesCount() => 0; // use 2 different BsonTypeCode for true|false 21 | 22 | public override int GetHashCode() => this.Value.GetHashCode(); 23 | 24 | #region Implement CompareTo 25 | 26 | public override int CompareTo(BsonValue other, Collation collation) 27 | { 28 | if (other is BsonBoolean otherBoolean) return this.Value.CompareTo(otherBoolean.Value); 29 | 30 | return this.CompareType(other); 31 | } 32 | 33 | #endregion 34 | 35 | #region Convert Types 36 | 37 | public override bool ToBoolean() => this.Value; 38 | 39 | public override int ToInt32() => this.Value ? 1 : 0; 40 | 41 | public override long ToInt64() => this.Value ? 1 : 0; 42 | 43 | public override double ToDouble() => this.Value ? 1 : 0; 44 | 45 | public override decimal ToDecimal() => this.Value ? 1 : 0; 46 | 47 | #endregion 48 | } 49 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonDateTime.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a DateTime value in Bson object model 5 | /// 6 | internal class BsonDateTime : BsonValue 7 | { 8 | public static readonly DateTime UnixEpoch = new (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 9 | 10 | public DateTime Value { get; } 11 | 12 | public BsonDateTime(DateTime value) 13 | { 14 | this.Value = value; 15 | } 16 | 17 | public override BsonType Type => BsonType.DateTime; 18 | 19 | public override int GetBytesCount() => 8; 20 | 21 | public override int GetHashCode() => this.Value.GetHashCode(); 22 | 23 | #region Implement CompareTo 24 | 25 | public override int CompareTo(BsonValue other, Collation collation) 26 | { 27 | if (other is BsonDateTime otherDateTime) return this.Value.CompareTo(otherDateTime.Value); 28 | 29 | return this.CompareType(other); 30 | } 31 | 32 | #endregion 33 | } 34 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonDecimal.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent an integer value in Bson object model 5 | /// 6 | internal class BsonDecimal : BsonValue 7 | { 8 | public static BsonDecimal Zero = new(0); 9 | public static BsonDecimal One = new(1); 10 | public static BsonDecimal MinusOne = new(-1); 11 | 12 | public decimal Value { get; } 13 | 14 | public BsonDecimal(decimal value) 15 | { 16 | this.Value = value; 17 | } 18 | 19 | public override BsonType Type => BsonType.Decimal; 20 | 21 | public override int GetBytesCount() => sizeof(decimal); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonDecimal otherDecimal) return this.Value.CompareTo(otherDecimal.Value); 30 | if (other is BsonInt32 otherInt32) return this.Value.CompareTo(otherInt32.ToDecimal()); 31 | if (other is BsonInt64 otherInt64) return this.Value.CompareTo(otherInt64.ToDecimal()); 32 | if (other is BsonDouble otherDouble) return this.Value.CompareTo(otherDouble.ToDecimal()); 33 | 34 | return this.CompareType(other); 35 | } 36 | 37 | #endregion 38 | 39 | #region Convert Types 40 | 41 | public override bool ToBoolean() => this.Value != 0; 42 | 43 | public override int ToInt32() => Convert.ToInt32(this.Value); 44 | 45 | public override long ToInt64() => Convert.ToInt64(this.Value); 46 | 47 | public override double ToDouble() => Convert.ToDouble(this.Value); 48 | 49 | public override decimal ToDecimal() => this.Value; 50 | 51 | #endregion 52 | } 53 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonDouble.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a double value in Bson object model 5 | /// 6 | internal class BsonDouble : BsonValue 7 | { 8 | public static BsonDouble Zero = new(0); 9 | public static BsonDouble One = new(1); 10 | public static BsonDouble MinusOne = new(-1); 11 | 12 | public double Value { get; } 13 | 14 | public BsonDouble(double value) 15 | { 16 | this.Value = value; 17 | } 18 | 19 | public override BsonType Type => BsonType.Double; 20 | 21 | public override int GetBytesCount() => sizeof(double); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonDouble otherDouble) return this.Value.CompareTo(otherDouble.Value); 30 | if (other is BsonInt32 otherInt32) return this.Value.CompareTo(otherInt32.ToDouble()); 31 | if (other is BsonInt64 otherInt64) return this.Value.CompareTo(otherInt64.ToDouble()); 32 | if (other is BsonDecimal otherDecimal) return this.ToDecimal().CompareTo(otherDecimal.Value); 33 | 34 | return this.CompareType(other); 35 | } 36 | 37 | #endregion 38 | 39 | #region Convert Types 40 | 41 | public override bool ToBoolean() => this.Value != 0; 42 | 43 | public override int ToInt32() => Convert.ToInt32(this.Value); 44 | 45 | public override long ToInt64() => Convert.ToInt64(this.Value); 46 | 47 | public override double ToDouble() => this.Value; 48 | 49 | public override decimal ToDecimal() => Convert.ToDecimal(this.Value); 50 | 51 | #endregion 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonGuid.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a Guid value in Bson object model 5 | /// 6 | internal class BsonGuid : BsonValue 7 | { 8 | public static BsonGuid Empty = new (Guid.Empty); 9 | 10 | public Guid Value { get; } 11 | 12 | public BsonGuid(Guid value) 13 | { 14 | if (value == null) throw new ArgumentNullException(nameof(value)); 15 | 16 | this.Value = value; 17 | } 18 | 19 | public override BsonType Type => BsonType.Guid; 20 | 21 | public override int GetBytesCount() => 16; 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonGuid otherGuid) return this.Value.CompareTo(otherGuid.Value); 30 | 31 | return this.CompareType(other); 32 | } 33 | 34 | #endregion 35 | } 36 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonInt32.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent an integer value in Bson object model 5 | /// 6 | public class BsonInt32 : BsonValue 7 | { 8 | public static BsonInt32 Zero = new(0); 9 | public static BsonInt32 One = new(1); 10 | public static BsonInt32 MinusOne = new(-1); 11 | 12 | public int Value { get; } 13 | 14 | public BsonInt32(int value) 15 | { 16 | this.Value = value; 17 | } 18 | 19 | public override BsonType Type => BsonType.Int32; 20 | 21 | public override int GetBytesCount() => sizeof(int); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonInt32 otherInt32) return this.Value.CompareTo(otherInt32.Value); 30 | if (other is BsonInt64 otherInt64) return this.ToInt64().CompareTo(otherInt64.Value); 31 | if (other is BsonDouble otherDouble) return this.ToDouble().CompareTo(otherDouble.Value); 32 | if (other is BsonDecimal otherDecimal) return this.ToDecimal().CompareTo(otherDecimal.Value); 33 | 34 | return this.CompareType(other); 35 | } 36 | 37 | #endregion 38 | 39 | #region Implicit Ctor 40 | 41 | public static implicit operator int(BsonInt32 value) => value.AsInt32; 42 | 43 | public static implicit operator BsonInt32(int value) => value switch 44 | { 45 | -1 => BsonInt32.MinusOne, 46 | 0 => BsonInt32.Zero, 47 | 1 => BsonInt32.One, 48 | _ => new BsonInt32(value), 49 | }; 50 | 51 | #endregion 52 | 53 | #region Convert Types 54 | 55 | public override bool ToBoolean() => this.Value != 0; 56 | 57 | public override int ToInt32() => this.Value; 58 | 59 | public override long ToInt64() => this.Value; 60 | 61 | public override double ToDouble() => this.Value; 62 | 63 | public override decimal ToDecimal() => this.Value; 64 | 65 | #endregion 66 | } 67 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonInt64.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent an Int64 value in Bson object model 5 | /// 6 | internal class BsonInt64 : BsonValue 7 | { 8 | public static BsonInt64 Zero = new(0); 9 | public static BsonInt64 One = new(1); 10 | public static BsonInt64 MinusOne = new(-1); 11 | 12 | public long Value { get; } 13 | 14 | public BsonInt64(long value) 15 | { 16 | this.Value = value; 17 | } 18 | 19 | public override BsonType Type => BsonType.Int64; 20 | 21 | public override int GetBytesCount() => sizeof(long); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonInt64 otherInt64) return this.Value.CompareTo(otherInt64.Value); 30 | if (other is BsonInt32 otherInt32) return this.Value.CompareTo(otherInt32.ToInt64()); 31 | if (other is BsonDouble otherDouble) return this.Value.CompareTo(otherDouble.Value); 32 | if (other is BsonDecimal otherDecimal) return this.Value.CompareTo(otherDecimal.Value); 33 | 34 | return this.CompareType(other); 35 | } 36 | 37 | #endregion 38 | 39 | #region Convert Types 40 | 41 | public override bool ToBoolean() => this.Value != 0; 42 | 43 | public override int ToInt32() => Convert.ToInt32(this.Value); 44 | 45 | public override long ToInt64() => this.Value; 46 | 47 | public override double ToDouble() => this.Value; 48 | 49 | public override decimal ToDecimal() => this.Value; 50 | 51 | #endregion 52 | } 53 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonMaxValue.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a max value constant in Bson object model 5 | /// 6 | internal class BsonMaxValue : BsonValue 7 | { 8 | public override BsonType Type => BsonType.MaxValue; 9 | 10 | public override int GetBytesCount() => 0; 11 | 12 | public override int GetHashCode() => this.Type.GetHashCode(); 13 | 14 | #region Implement CompareTo 15 | 16 | public override int CompareTo(BsonValue other, Collation collation) 17 | { 18 | if (other is BsonMaxValue) return 0; 19 | 20 | return 1; // all types are lower than MaxValue 21 | } 22 | 23 | #endregion 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonMinValue.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a min value constant in Bson object model 5 | /// 6 | internal class BsonMinValue : BsonValue 7 | { 8 | public override BsonType Type => BsonType.MinValue; 9 | 10 | public override int GetBytesCount() => 0; 11 | 12 | public override int GetHashCode() => this.Type.GetHashCode(); 13 | 14 | #region Implement CompareTo 15 | 16 | public override int CompareTo(BsonValue other, Collation collation) 17 | { 18 | if (other is BsonMinValue) return 0; 19 | 20 | return -1; // all types are heigher than MinValue 21 | } 22 | 23 | #endregion 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonNull.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a null value constant in Bson object model (BsonNull is a valid value) 5 | /// 6 | internal class BsonNull : BsonValue 7 | { 8 | public override BsonType Type => BsonType.Null; 9 | 10 | public override int GetBytesCount() => 0; 11 | 12 | public override int GetHashCode() => this.Type.GetHashCode(); 13 | 14 | #region Implement CompareTo 15 | 16 | public override int CompareTo(BsonValue other, Collation collation) 17 | { 18 | if (other is BsonNull) return 0; 19 | 20 | return this.CompareType(other); 21 | } 22 | 23 | #endregion 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonObjectId.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent an ObjectId value (12 bytes sequencial guid-like) in Bson object model 5 | /// 6 | internal class BsonObjectId : BsonValue 7 | { 8 | public static BsonObjectId Empty = new (ObjectId.Empty); 9 | 10 | public ObjectId Value { get; } 11 | 12 | public BsonObjectId(ObjectId value) 13 | { 14 | this.Value = value; 15 | } 16 | 17 | public override BsonType Type => BsonType.ObjectId; 18 | 19 | public override int GetBytesCount() => 12; 20 | 21 | public override int GetHashCode() => this.Value.GetHashCode(); 22 | 23 | #region Implement CompareTo 24 | 25 | public override int CompareTo(BsonValue other, Collation collation) 26 | { 27 | if (other is BsonObjectId otherObjectId) return this.Value.CompareTo(otherObjectId.Value); 28 | 29 | return this.CompareType(other); 30 | } 31 | 32 | #endregion 33 | } 34 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonString.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent a String value in Bson object model 5 | /// 6 | internal class BsonString : BsonValue 7 | { 8 | public static BsonString Emtpy = new(""); 9 | 10 | public static BsonString Id = new("_id"); 11 | 12 | public string Value { get; } 13 | 14 | public BsonString(string value) 15 | { 16 | this.Value = value ?? throw new ArgumentNullException(nameof(value)); 17 | } 18 | 19 | public override BsonType Type => BsonType.String; 20 | 21 | public override int GetBytesCount() => Encoding.UTF8.GetByteCount(this.Value); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | #region Implement CompareTo 26 | 27 | public override int CompareTo(BsonValue other, Collation collation) 28 | { 29 | if (other is BsonString otherString) return collation.Compare(this.Value, otherString.Value); 30 | 31 | return this.CompareType(other); 32 | } 33 | 34 | #endregion 35 | 36 | #region Implicit Ctor 37 | 38 | public static implicit operator string(BsonString value) => value.Value; 39 | 40 | public static implicit operator BsonString(string value) 41 | { 42 | if (value == null) throw new ArgumentNullException(nameof(value)); 43 | 44 | if (value.Length == 0) return Emtpy; 45 | 46 | return new BsonString(value); 47 | } 48 | 49 | #endregion 50 | 51 | #region Convert Types 52 | 53 | public override bool ToBoolean() => Convert.ToBoolean(this.Value); 54 | 55 | public override int ToInt32() => Convert.ToInt32(this.Value, CultureInfo.InvariantCulture.NumberFormat); 56 | 57 | public override long ToInt64() => Convert.ToInt64(this.Value, CultureInfo.InvariantCulture.NumberFormat); 58 | 59 | public override double ToDouble() => Convert.ToDouble(this.Value, CultureInfo.InvariantCulture.NumberFormat); 60 | 61 | public override decimal ToDecimal() => Convert.ToDecimal(this.Value, CultureInfo.InvariantCulture.NumberFormat); 62 | 63 | #endregion 64 | } 65 | -------------------------------------------------------------------------------- /src/LiteDB/Document/ObjectModel/BsonType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// All supported BsonTypes in sort order 5 | /// 6 | public enum BsonType : byte 7 | { 8 | MinValue = 0, 9 | 10 | Null = 1, 11 | 12 | Int32 = 2, 13 | Int64 = 3, 14 | Double = 4, 15 | Decimal = 5, 16 | 17 | String = 6, 18 | 19 | Document = 7, 20 | Array = 8, 21 | 22 | Binary = 9, 23 | ObjectId = 10, 24 | Guid = 11, 25 | DateTime = 12, 26 | Boolean = 13, 27 | // 13 reserved for (true|false) 28 | 29 | MaxValue = 31 30 | } 31 | 32 | public static class BsonTypeExtensions 33 | { 34 | /// 35 | /// Checks if BsonType is a numeric data type (can be cast to be compare) 36 | /// 37 | public static bool IsNumeric(this BsonType bsonType) => 38 | bsonType == BsonType.Int32 || 39 | bsonType == BsonType.Double || 40 | bsonType == BsonType.Int64 || 41 | bsonType == BsonType.Decimal || 42 | bsonType == BsonType.Boolean; 43 | } 44 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Commands/Engine.Execute.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public partial class LiteEngine : ILiteEngine 4 | { 5 | public ValueTask ExecuteAsync(string sql) 6 | => this.ExecuteAsync(sql, BsonDocument.Empty); 7 | 8 | public ValueTask ExecuteAsync(string sql, BsonValue args0) 9 | => this.ExecuteAsync(sql, new BsonDocument { ["0"] = args0 }); 10 | 11 | public ValueTask ExecuteAsync(string sql, BsonValue args0, BsonValue args1) 12 | => this.ExecuteAsync(sql, new BsonDocument { ["0"] = args0, ["1"] = args1 }); 13 | 14 | /// 15 | /// Parse and execute an ah-hoc sql statement 16 | /// 17 | public ValueTask ExecuteAsync(string sql, BsonDocument parameters) 18 | { 19 | var collation = _factory.FileHeader.Collation; 20 | var tokenizer = new Tokenizer(sql); 21 | var parser = new SqlParser(tokenizer, collation); 22 | 23 | var statement = parser.ParseStatement(); 24 | 25 | var result = statement.ExecuteAsync(_factory, parameters); 26 | 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Commands/Engine.ExecuteQuery.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public partial class LiteEngine : ILiteEngine 4 | { 5 | public ValueTask ExecuteReaderAsync(string sql) 6 | => this.ExecuteReaderAsync(sql, BsonDocument.Empty); 7 | 8 | public ValueTask ExecuteReaderAsync(string sql, BsonValue args0) 9 | => this.ExecuteReaderAsync(sql, new BsonDocument { ["0"] = args0 }); 10 | 11 | public ValueTask ExecuteReaderAsync(string sql, BsonValue args0, BsonValue args1) 12 | => this.ExecuteReaderAsync(sql, new BsonDocument { ["0"] = args0, ["1"] = args1 }); 13 | 14 | /// 15 | /// Parse and execute an ah-hoc sql statement 16 | /// 17 | public ValueTask ExecuteReaderAsync(string sql, BsonDocument parameters) 18 | { 19 | var collation = _factory.FileHeader.Collation; 20 | var tokenizer = new Tokenizer(sql); 21 | var parser = new SqlParser(tokenizer, collation); 22 | 23 | var statement = parser.ParseStatement(); 24 | 25 | var reader = statement.ExecuteReaderAsync(_factory, parameters); 26 | 27 | return reader; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Commands/Engine.Open.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public partial class LiteEngine : ILiteEngine 4 | { 5 | public async Task OpenAsync() 6 | { 7 | var lockService = _factory.LockService; 8 | var diskService = _factory.DiskService; 9 | var logService = _factory.LogService; 10 | var allocationMapService = _factory.AllocationMapService; 11 | var masterService = _factory.MasterService; 12 | var recoveryService = _factory.RecoveryService; 13 | 14 | if (_factory.State != EngineState.Close) throw ERR("must be closed"); 15 | 16 | // clean last database exception 17 | _factory.Exception = null; 18 | 19 | // must run in exclusive mode 20 | await lockService.EnterExclusiveAsync(); 21 | 22 | if (_factory.State != EngineState.Close) throw ERR("must be closed"); 23 | 24 | try 25 | { 26 | var stream = diskService.GetDiskWriter(); 27 | 28 | // open/create data file and returns file header 29 | _factory.FileHeader = await diskService.InitializeAsync(); 30 | 31 | // checks if datafile was finish correctly 32 | if (_factory.FileHeader.IsDirty) 33 | { 34 | _factory.State = EngineState.Recovery; 35 | 36 | // do a database recovery 37 | await recoveryService.DoRecoveryAsync(); 38 | 39 | stream.WriteFlag(FileHeader.P_IS_DIRTY, 0); 40 | 41 | _factory.FileHeader.IsDirty = false; 42 | } 43 | 44 | // initialize log service based on disk 45 | logService.Initialize(); 46 | 47 | // initialize AM service 48 | allocationMapService.Initialize(); 49 | 50 | // read $master 51 | masterService.Initialize(); 52 | 53 | // update header/state 54 | _factory.State = EngineState.Open; 55 | 56 | // release exclusive 57 | lockService.ExitExclusive(); 58 | 59 | } 60 | catch (Exception ex) 61 | { 62 | ex.HandleError(_factory); 63 | throw; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Commands/Engine.Shutdown.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public partial class LiteEngine : ILiteEngine 4 | { 5 | public async Task ShutdownAsync() 6 | { 7 | if (_factory.State != EngineState.Open) throw ERR("must be open"); 8 | 9 | var lockService = _factory.LockService; 10 | var diskService = _factory.DiskService; 11 | var logService = _factory.LogService; 12 | var allocationMapService = _factory.AllocationMapService; 13 | var stream = diskService.GetDiskWriter(); 14 | 15 | // must enter in exclusive lock 16 | await lockService.EnterExclusiveAsync(); 17 | 18 | // set engine state to shutdown 19 | _factory.State = EngineState.Shutdown; 20 | 21 | // do checkpoint 22 | await logService.CheckpointAsync(true, false); 23 | 24 | // persist all dirty amp into disk 25 | allocationMapService.WriteAllChanges(); 26 | 27 | // if file was changed, update file header check byte 28 | if (_factory.FileHeader.IsDirty) 29 | { 30 | stream.WriteFlag(FileHeader.P_IS_DIRTY, 0); 31 | 32 | _factory.FileHeader.IsDirty = false; 33 | } 34 | 35 | // release exclusive 36 | lockService.ExitExclusive(); 37 | 38 | // call sync dispose (release disk/memory) 39 | _factory.Dispose(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/DocumentStoreFactory.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Document store factory to cache results 5 | /// 6 | [AutoInterface] 7 | internal class DocumentStoreFactory : IDocumentStoreFactory 8 | { 9 | public IDocumentStore GetUserCollection(string name) 10 | { 11 | //TODO: como reaproveitar? cache direto pode não ser uma boa 12 | return new UserCollectionStore(name); 13 | } 14 | 15 | public IDocumentStore GetVirtualCollection(string name, BsonDocument parameters) 16 | { 17 | //TODO: vai conter SWITCH para decidir 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/FileJson/FileJsonDocumentStore.cs: -------------------------------------------------------------------------------- 1 | //namespace LiteDB.Engine; 2 | 3 | //internal class SubQueryStore : ISourceStore 4 | //{ 5 | // public string Name { get; } 6 | // public byte ColID { get; } 7 | // public IReadOnlyList Indexes { get; } 8 | 9 | // private Query _subQuery; 10 | 11 | // public SubQueryStore(Query query) 12 | // { 13 | // _subQuery = query; 14 | // } 15 | 16 | // internal void Loa0d(IMasterService masterService) 17 | // { 18 | // } 19 | 20 | // public IPipeEnumerator GetPipeEnumerator(BsonExpression expression) 21 | // { 22 | // throw new NotImplementedException(); 23 | // } 24 | //} 25 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/Interfaces/IDataService.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IDataService 4 | { 5 | /// 6 | /// Insert BsonDocument into new data pages 7 | /// 8 | RowID InsertDocument(byte colID, BsonDocument doc); 9 | 10 | /// 11 | /// Update existing document in a single or multiple pages 12 | /// 13 | void UpdateDocument(RowID dataBlockID, BsonDocument doc); 14 | 15 | /// 16 | /// Read a single document in a single/multiple pages 17 | /// 18 | BsonReadResult ReadDocument(RowID dataBlockID, string[] fields); 19 | 20 | /// 21 | /// Delete a full document from a single or multiple pages 22 | /// 23 | void DeleteDocument(RowID dataBlockID); 24 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/Interfaces/IDocumentStore.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IDocumentStore : IDisposable 4 | { 5 | byte ColID { get; } 6 | string Name { get; } 7 | 8 | /// 9 | /// Should be call this method just after enter in execution on statement. Will load internal collection 10 | /// 11 | void Initialize(IMasterService masterService); 12 | 13 | IReadOnlyList GetIndexes(); 14 | 15 | (IDataService dataService, IIndexService indexService) GetServices(IServicesFactory factory, ITransaction transaction); 16 | 17 | IPipeEnumerator GetPipeEnumerator(BsonExpression expression); 18 | 19 | // Dipose will be run in statement dispose 20 | } 21 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/Interfaces/IIndexService.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IIndexService 4 | { 5 | /// 6 | /// Create head and tail nodes for a new index 7 | /// 8 | (RowID head, RowID tail) CreateHeadTailNodes(byte colID); 9 | 10 | /// 11 | /// Insert a new node index inside an collection index. Flip coin to know level 12 | /// 13 | IndexNodeResult AddNode(byte colID, IndexDocument index, BsonValue key, RowID dataBlockID, IndexNodeResult last, out bool defrag); 14 | 15 | /// 16 | /// Flip coin (skipped list): returns how many levels the node will have (starts in 1, max of INDEX_MAX_LEVELS) 17 | /// 18 | int Flip(); 19 | 20 | /// 21 | /// Get a node/pageBuffer inside a page using RowID. IndexNodeID must be a valid position 22 | /// 23 | IndexNodeResult GetNode(RowID indexNodeID); 24 | 25 | /// 26 | /// Find first node that index match with value . 27 | /// If index are unique, return unique value - if index are not unique, return first found (can start, middle or end) 28 | /// If not found but sibling = true and key are not found, returns next value index node (if order = Asc) or prev node (if order = Desc) 29 | /// 30 | IndexNodeResult Find(IndexDocument index, BsonValue key, bool sibling, int order); 31 | 32 | /// 33 | /// Deletes all indexes nodes from pkNode 34 | /// 35 | void DeleteAll(IndexNodeResult nodeResult); 36 | } 37 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/SubQuery/SubQueryStore.cs: -------------------------------------------------------------------------------- 1 | //namespace LiteDB.Engine; 2 | 3 | //internal class SubQueryStore : ISourceStore 4 | //{ 5 | // public string Name { get; } 6 | // public byte ColID { get; } 7 | // public IReadOnlyList Indexes { get; } 8 | 9 | // private Query _subQuery; 10 | 11 | // public SubQueryStore(Query query) 12 | // { 13 | // _subQuery = query; 14 | // } 15 | 16 | // internal void Loa0d(IMasterService masterService) 17 | // { 18 | // } 19 | 20 | // public IPipeEnumerator GetPipeEnumerator(BsonExpression expression) 21 | // { 22 | // throw new NotImplementedException(); 23 | // } 24 | //} 25 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/DocumentStore/UserCollection/UserCollectionStore.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class UserCollectionStore : IDocumentStore 4 | { 5 | private readonly string _name; 6 | private CollectionDocument? _collection; // will load on Initialize() 7 | 8 | public byte ColID => _collection?.ColID ?? 0; 9 | public string Name => _name; 10 | public IReadOnlyList Indexes => _collection?.Indexes ?? (IReadOnlyList)Array.Empty(); 11 | 12 | 13 | public UserCollectionStore(string name) 14 | { 15 | _name = name; 16 | } 17 | 18 | public void Initialize(IMasterService masterService) 19 | { 20 | var master = masterService.GetMaster(false); 21 | 22 | if (master.Collections.TryGetValue(_name, out var collection)) 23 | { 24 | _collection = collection; 25 | } 26 | else 27 | { 28 | throw ERR($"Collection {_name} does not exist"); 29 | } 30 | } 31 | 32 | public IReadOnlyList GetIndexes() => 33 | _collection!.Indexes; 34 | 35 | public (IDataService dataService, IIndexService indexService) GetServices(IServicesFactory factory, ITransaction transaction) => 36 | (factory.CreateDataService(transaction), factory.CreateIndexService(transaction)); 37 | 38 | public IPipeEnumerator GetPipeEnumerator(BsonExpression expression) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public void Dispose() 44 | { 45 | // in user collection, there is nothing to dispose 46 | _collection = null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/LiteEngine.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// A public class that take care of all engine data structure access - it´s basic implementation of a NoSql database 5 | /// Its isolated from complete solution - works on low level only (no linq, no poco... just BSON objects) 6 | /// [ThreadSafe] 7 | /// 8 | [AutoInterface(typeof(IDisposable))] 9 | public partial class LiteEngine : ILiteEngine 10 | { 11 | private readonly IServicesFactory _factory; 12 | 13 | public EngineState State => _factory.State; 14 | 15 | #region Ctor 16 | 17 | /// 18 | /// Initialize LiteEngine using in-memory database 19 | /// 20 | public LiteEngine() 21 | : this(new EngineSettings { DataStream = new MemoryStream() }) 22 | { 23 | } 24 | 25 | /// 26 | /// Initialize LiteEngine using file system 27 | /// 28 | public LiteEngine(string filename) 29 | : this (new EngineSettings { Filename = filename }) 30 | { 31 | } 32 | 33 | /// 34 | /// Initialize LiteEngine using all engine settings 35 | /// 36 | public LiteEngine(EngineSettings settings) 37 | : this (new ServicesFactory(settings)) 38 | { 39 | } 40 | 41 | /// 42 | /// To initialize LiteEngine we need classes factory and engine settings 43 | /// Current version still using IServiceFactory as internal... 44 | /// 45 | internal LiteEngine(IServicesFactory factory) 46 | { 47 | _factory = factory; 48 | } 49 | 50 | #endregion 51 | 52 | // to see all methods, look at /Commands files (partial class from I__LiteEngine) 53 | 54 | public void Dispose() 55 | { 56 | this.Dispose(true); 57 | GC.SuppressFinalize(this); 58 | } 59 | 60 | ~LiteEngine() 61 | { 62 | this.Dispose(false); 63 | } 64 | 65 | protected virtual void Dispose(bool disposing) 66 | { 67 | //if (_disposed) return; 68 | 69 | if (disposing) 70 | { 71 | //_services.CloseAsync().Wait; 72 | } 73 | 74 | //_disposed = true; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/DiskService/StreamFactory/FileSortStreamFactory.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// File implementation for sort stream 5 | /// 6 | internal class FileSortStreamFactory : IStreamFactory 7 | { 8 | private readonly string _filename; 9 | 10 | public string Name => Path.GetFileName(_filename); 11 | 12 | public FileSortStreamFactory(string dataFilename) 13 | { 14 | _filename = FileHelper.GetSufixFile(dataFilename, "-sort", true); 15 | } 16 | 17 | public void Delete() 18 | { 19 | File.Delete(_filename); 20 | } 21 | 22 | public bool Exists() 23 | { 24 | return File.Exists(_filename); 25 | } 26 | 27 | public long GetLength() 28 | { 29 | // if file don't exists, returns 0 30 | if (!this.Exists()) return 0; 31 | 32 | // get physical file length from OS 33 | var length = new FileInfo(_filename).Length; 34 | 35 | return length; 36 | } 37 | 38 | /// 39 | /// Get a new stream (open/create) from sort temp 40 | /// 41 | public Stream GetStream(bool canWrite, FileOptions options) 42 | { 43 | var stream = new FileStream( 44 | _filename, 45 | FileMode.OpenOrCreate, 46 | FileAccess.ReadWrite, 47 | FileShare.ReadWrite, 48 | PAGE_SIZE, 49 | FileOptions.Asynchronous); 50 | 51 | return stream; 52 | } 53 | 54 | public bool DisposeOnClose => true; 55 | } 56 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/DiskService/StreamFactory/FileStreamFactory.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class FileStreamFactory : IStreamFactory 4 | { 5 | private readonly string _filename; 6 | private readonly bool _readOnly; 7 | 8 | public string Name => Path.GetFileName(_filename); 9 | 10 | public FileStreamFactory(string filename, bool readOnly) 11 | { 12 | _filename = filename; 13 | _readOnly = readOnly; 14 | } 15 | 16 | public void Delete() 17 | { 18 | File.Delete(_filename); 19 | } 20 | 21 | public bool Exists() 22 | { 23 | return File.Exists(_filename); 24 | } 25 | 26 | public long GetLength() 27 | { 28 | // if file don't exists, returns 0 29 | if (!this.Exists()) return 0; 30 | 31 | // get physical file length from OS 32 | var length = new FileInfo(_filename).Length; 33 | 34 | return length; 35 | } 36 | 37 | /// 38 | /// Open an existing data file and return FileStream implementation 39 | /// 40 | public Stream GetStream(bool canWrite, FileOptions options) 41 | { 42 | var write = canWrite && (_readOnly == false); 43 | 44 | var stream = new FileStream( 45 | _filename, 46 | _readOnly ? FileMode.Open : FileMode.OpenOrCreate, 47 | write ? FileAccess.ReadWrite : FileAccess.Read, 48 | write ? FileShare.Read : FileShare.ReadWrite, 49 | PAGE_SIZE, 50 | options); 51 | 52 | return stream; 53 | } 54 | 55 | public bool DisposeOnClose => true; 56 | } 57 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/DiskService/StreamFactory/IStreamFactory.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IStreamFactory 4 | { 5 | string Name { get; } 6 | bool Exists(); 7 | long GetLength(); 8 | void Delete(); 9 | Stream GetStream(bool canWrite, FileOptions options); 10 | bool DisposeOnClose { get; } 11 | } 12 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/DiskService/StreamFactory/MemoryStreamFactory.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class MemoryStreamFactory : IStreamFactory 4 | { 5 | private readonly Stream _stream; 6 | 7 | public string Name => ":memory:"; 8 | 9 | public MemoryStreamFactory(Stream stream) 10 | { 11 | _stream = stream; 12 | } 13 | 14 | public void Delete() 15 | { 16 | } 17 | 18 | public bool Exists() 19 | { 20 | return true; 21 | } 22 | 23 | public long GetLength() 24 | { 25 | return _stream.Length; 26 | } 27 | 28 | public Stream GetStream(bool canWrite, FileOptions options) 29 | { 30 | return _stream; 31 | } 32 | 33 | public bool DisposeOnClose => true; 34 | } 35 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/LockService/AsyncReadWriteLock.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Implement multiple readers/single writer for async task 5 | /// 6 | internal class AsyncReaderWriterLock : IDisposable 7 | { 8 | private readonly SemaphoreSlim _readSemaphore = new (1, 1); 9 | private readonly SemaphoreSlim _writeSemaphore = new (1, 1); 10 | private readonly TimeSpan _timeout; 11 | private int _readerCount; 12 | 13 | public int ReaderCount => _readerCount; 14 | 15 | public AsyncReaderWriterLock(TimeSpan timeout) 16 | { 17 | //TODO: antes de usar o timeout, verificar que ao adicionar o metodo Wait retorna bool 18 | _timeout = timeout; 19 | } 20 | 21 | public async ValueTask AcquireWriterLock(CancellationToken token = default) 22 | { 23 | await _writeSemaphore.WaitAsync(_timeout, token).ConfigureAwait(false); 24 | 25 | try 26 | { 27 | await _readSemaphore.WaitAsync(_timeout, token).ConfigureAwait(false); 28 | } 29 | catch 30 | { 31 | _writeSemaphore.Release(); 32 | throw; 33 | } 34 | } 35 | 36 | public void ReleaseWriterLock() 37 | { 38 | _readSemaphore.Release(); 39 | _writeSemaphore.Release(); 40 | } 41 | 42 | public async ValueTask AcquireReaderLock(CancellationToken token = default) 43 | { 44 | await _writeSemaphore.WaitAsync(_timeout, token).ConfigureAwait(false); 45 | 46 | if (Interlocked.Increment(ref _readerCount) == 1) 47 | { 48 | try 49 | { 50 | await _readSemaphore.WaitAsync(_timeout, token).ConfigureAwait(false); 51 | } 52 | catch 53 | { 54 | Interlocked.Decrement(ref _readerCount); 55 | _writeSemaphore.Release(); 56 | 57 | throw; 58 | } 59 | } 60 | 61 | _writeSemaphore.Release(); 62 | } 63 | 64 | public void ReleaseReaderLock() 65 | { 66 | if (Interlocked.Decrement(ref _readerCount) == 0) 67 | { 68 | _readSemaphore.Release(); 69 | } 70 | } 71 | 72 | public void Dispose() 73 | { 74 | _writeSemaphore.Dispose(); 75 | _readSemaphore.Dispose(); 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/MasterService/ObjectModel/CollectionDocument.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class CollectionDocument 4 | { 5 | public required byte ColID { get; init; } 6 | public required string Name { get; set; } // can be changed in RenameCollection 7 | public required List Indexes { get; init; } 8 | 9 | public IndexDocument PK => this.Indexes[0]; 10 | 11 | public CollectionDocument() 12 | { 13 | } 14 | 15 | /// 16 | /// Clone object instance constructor 17 | /// 18 | public CollectionDocument(CollectionDocument other) 19 | { 20 | this.ColID = other.ColID; 21 | this.Name = other.Name; 22 | this.Indexes = new List(other.Indexes); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/MasterService/ObjectModel/IndexDocument.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class IndexDocument 4 | { 5 | public required byte Slot { get; init; } 6 | public required string Name { get; init; } 7 | public required BsonExpression Expression { get; init; } 8 | public required bool Unique { get; init; } 9 | public required RowID HeadIndexNodeID { get; init; } 10 | public required RowID TailIndexNodeID { get; init; } 11 | 12 | public IndexDocument() 13 | { 14 | } 15 | 16 | /// 17 | /// Clone object instance constructor 18 | /// 19 | public IndexDocument(IndexDocument other) 20 | { 21 | this.Slot = other.Slot; 22 | this.Name = other.Name; 23 | this.Expression = other.Expression; 24 | this.Unique = other.Unique; 25 | this.HeadIndexNodeID = other.HeadIndexNodeID; 26 | this.TailIndexNodeID = other.TailIndexNodeID; 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/MasterService/ObjectModel/MasterDocument.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class MasterDocument 4 | { 5 | 6 | /// 7 | /// A dictionary with all collection indexed by collection name 8 | /// 9 | public Dictionary Collections { get; init; } = new(StringComparer.OrdinalIgnoreCase); 10 | 11 | /// 12 | /// Get current database pragma values 13 | /// 14 | public PragmaDocument Pragmas { get; init; } = new(); 15 | 16 | /// 17 | /// Initial master document 18 | /// 19 | public MasterDocument() 20 | { 21 | } 22 | 23 | /// 24 | /// Clone object instance constructor 25 | /// 26 | public MasterDocument(MasterDocument other) 27 | { 28 | this.Collections = new(other.Collections); 29 | this.Pragmas = new PragmaDocument(other.Pragmas); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/MasterService/ObjectModel/PragmaDocument.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class PragmaDocument 4 | { 5 | /// 6 | /// Internal user version control to detect database changes 7 | /// 8 | public int UserVersion { get; set; } = 0; 9 | 10 | /// 11 | /// Max limit of datafile (in bytes) (default: MaxValue) 12 | /// 13 | public int LimitSizeID { get; set; } = 0; 14 | 15 | /// 16 | /// When LOG file gets larger than checkpoint size (in pages), do a soft checkpoint (and also do a checkpoint at shutdown) 17 | /// Checkpoint = 0 means there's no auto-checkpoint nor shutdown checkpoint 18 | /// 19 | public int Checkpoint { get; set; } = CHECKPOINT_SIZE; 20 | 21 | /// 22 | /// Initial pragma values 23 | /// 24 | public PragmaDocument() 25 | { 26 | } 27 | 28 | /// 29 | /// Clone object instance constructor 30 | /// 31 | public PragmaDocument(PragmaDocument other) 32 | { 33 | this.UserVersion = other.UserVersion; 34 | this.LimitSizeID = other.LimitSizeID; 35 | this.Checkpoint = other.Checkpoint; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/AnyFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class AnyFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonValue _state = false; 8 | 9 | public AnyFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | if (_state == false) 19 | { 20 | _state = true; 21 | } 22 | } 23 | 24 | public BsonValue GetResult() 25 | { 26 | return _state; 27 | } 28 | 29 | public void Reset() 30 | { 31 | _state = false; 32 | } 33 | 34 | public override string ToString() 35 | { 36 | return $"ANY({_expr})"; 37 | } 38 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/ArrayFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class ArrayFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonArray _state = new BsonArray(); 8 | 9 | public ArrayFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | var result = _expr.Execute(document, null, collation); 19 | 20 | _state.Add(result); 21 | } 22 | 23 | public BsonValue GetResult() 24 | { 25 | return _state; 26 | } 27 | 28 | public void Reset() 29 | { 30 | _state = new BsonArray(); 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"ARRAY({_expr})"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/AvgFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class AvgFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private int _count = 0; 8 | private int _sumInt = 0; 9 | private long _sumLong = 0L; 10 | private double _sumDouble = 0d; 11 | private decimal _sumDecimal = 0m; 12 | 13 | public AvgFunc(BsonExpression expr) 14 | { 15 | _expr = expr; 16 | } 17 | 18 | public BsonExpression Expression => _expr; 19 | 20 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 21 | { 22 | var result = _expr.Execute(document, null, collation); 23 | 24 | _count++; 25 | 26 | if (result is BsonInt32 int32) _sumInt = unchecked(_sumInt + int32); 27 | else if (result is BsonInt64 int64) _sumLong = unchecked(_sumLong + int64); 28 | else if (result is BsonDouble double64) _sumDouble = unchecked(_sumDouble + double64); 29 | else if (result is BsonDecimal decimal128) _sumDecimal = unchecked(_sumDecimal + decimal128); 30 | else _count--; 31 | } 32 | 33 | public BsonValue GetResult() 34 | { 35 | if (_sumDecimal > 0) return (_sumDecimal + Convert.ToDecimal(_sumDouble) + _sumLong + _sumInt) / _count; 36 | if (_sumDouble > 0) return (_sumDouble + _sumLong + _sumInt) / _count; 37 | if (_sumLong > 0) return (_sumLong + _sumInt) / _count; 38 | if (_sumInt > 0) return (_sumInt) / _count; 39 | 40 | return 0; 41 | } 42 | 43 | public void Reset() 44 | { 45 | _count = _sumInt = 0; 46 | _sumLong = 0L; 47 | _sumDouble = 0d; 48 | _sumDecimal = 0m; 49 | } 50 | 51 | public override string ToString() 52 | { 53 | return $"AVG({_expr})"; 54 | } 55 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/CountFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class CountFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private long _state = 0; 8 | 9 | public CountFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | var result = _expr.Execute(document, null, collation); 19 | 20 | if (!result.IsNull) _state++; 21 | } 22 | 23 | public BsonValue GetResult() 24 | { 25 | return _state < int.MaxValue ? new BsonInt32((int)_state) : new BsonInt64(_state); 26 | } 27 | 28 | public void Reset() 29 | { 30 | _state = 0; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"COUNT({_expr})"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/FirstFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class FirstFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonValue _state = BsonValue.MinValue; 8 | 9 | public FirstFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | if (_state.IsMinValue) 19 | { 20 | var result = _expr.Execute(document, null, collation); 21 | 22 | _state = result; 23 | } 24 | } 25 | 26 | public BsonValue GetResult() 27 | { 28 | return _state.IsMinValue ? BsonValue.Null : _state; 29 | } 30 | 31 | public void Reset() 32 | { 33 | _state = BsonValue.MinValue; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return $"FIRST({_expr})"; 39 | } 40 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/IAggregateFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// 5 | public interface IAggregateFunc 6 | { 7 | BsonExpression Expression { get; } 8 | void Iterate(BsonValue key, BsonDocument document, Collation collation); 9 | BsonValue GetResult(); 10 | void Reset(); 11 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/LastFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class LastFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonValue _state = BsonValue.Null; 8 | 9 | public LastFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | var result = _expr.Execute(document, null, collation); 19 | 20 | _state = result; 21 | } 22 | 23 | public BsonValue GetResult() 24 | { 25 | return _state; 26 | } 27 | 28 | public void Reset() 29 | { 30 | _state = BsonValue.Null; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"LAST({_expr})"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/MaxFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class MaxFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonValue _state = BsonValue.MinValue; 8 | 9 | public MaxFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | var result = _expr.Execute(document, null, collation); 19 | 20 | if (result.CompareTo(_state) >= 0) 21 | { 22 | _state = result; 23 | } 24 | } 25 | 26 | public BsonValue GetResult() 27 | { 28 | return _state; 29 | } 30 | 31 | public void Reset() 32 | { 33 | _state = BsonValue.MinValue; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return $"MAX({_expr})"; 39 | } 40 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/MinFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class MinFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private BsonValue _state = BsonValue.MaxValue; 8 | 9 | public MinFunc(BsonExpression expr) 10 | { 11 | _expr = expr; 12 | } 13 | 14 | public BsonExpression Expression => _expr; 15 | 16 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 17 | { 18 | var result = _expr.Execute(document, null, collation); 19 | 20 | if (result.CompareTo(_state) <= 0) 21 | { 22 | _state = result; 23 | } 24 | } 25 | 26 | public BsonValue GetResult() 27 | { 28 | return _state; 29 | } 30 | 31 | public void Reset() 32 | { 33 | _state = BsonValue.MaxValue; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return $"MIN({_expr})"; 39 | } 40 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/AggreagteFunc/SumFunc.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public class SumFunc : IAggregateFunc 4 | { 5 | private readonly BsonExpression _expr; 6 | 7 | private int _sumInt = 0; 8 | private long _sumLong = 0L; 9 | private double _sumDouble = 0d; 10 | private decimal _sumDecimal = 0m; 11 | 12 | public SumFunc(BsonExpression expr) 13 | { 14 | _expr = expr; 15 | } 16 | 17 | public BsonExpression Expression => _expr; 18 | 19 | public void Iterate(BsonValue key, BsonDocument document, Collation collation) 20 | { 21 | var result = _expr.Execute(document, null, collation); 22 | 23 | if (result is BsonInt32 int32) _sumInt = unchecked(_sumInt + int32); 24 | else if (result is BsonInt64 int64) _sumLong = unchecked(_sumLong + int64); 25 | else if (result is BsonDouble double64) _sumDouble = unchecked(_sumDouble + double64); 26 | else if (result is BsonDecimal decimal128) _sumDecimal = unchecked(_sumDecimal + decimal128); 27 | } 28 | 29 | public BsonValue GetResult() 30 | { 31 | if (_sumDecimal > 0) return (_sumDecimal + Convert.ToDecimal(_sumDouble) + _sumLong + _sumInt); 32 | if (_sumDouble > 0) return (_sumDouble + _sumLong + _sumInt); 33 | if (_sumLong > 0) return (_sumLong + _sumInt); 34 | if (_sumInt > 0) return (_sumInt); 35 | 36 | return 0; 37 | } 38 | 39 | public void Reset() 40 | { 41 | _sumInt = 0; 42 | _sumLong = 0L; 43 | _sumDouble = 0d; 44 | _sumDecimal = 0m; 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return $"SUM({_expr})"; 50 | } 51 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/Indexes/IndexAllEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class IndexAllEnumerator : IPipeEnumerator 4 | { 5 | private readonly IndexDocument _indexDocument; 6 | private readonly int _order; 7 | private readonly bool _returnKey; 8 | 9 | private bool _init = false; 10 | private bool _eof = false; 11 | 12 | private RowID _next = RowID.Empty; // all nodes from right of first node found 13 | 14 | public IndexAllEnumerator( 15 | IndexDocument indexDocument, 16 | int order, 17 | bool returnKey) 18 | { 19 | _indexDocument = indexDocument; 20 | _order = order; 21 | _returnKey = returnKey; 22 | } 23 | 24 | public PipeEmit Emit => new(indexNodeID: true, dataBlockID: true, value: _returnKey); 25 | 26 | public unsafe PipeValue MoveNext(PipeContext context) 27 | { 28 | if (_eof) return PipeValue.Empty; 29 | 30 | var indexService = context.IndexService; 31 | 32 | var head = _order == Query.Ascending ? _indexDocument.HeadIndexNodeID : _indexDocument.TailIndexNodeID; 33 | var tail = _order == Query.Ascending ? _indexDocument.TailIndexNodeID : _indexDocument.HeadIndexNodeID; 34 | 35 | // in first run, gets head node 36 | if (_init == false) 37 | { 38 | _init = true; 39 | 40 | var first = indexService.GetNode(head); 41 | 42 | // get pointer to first element 43 | _next = first[0]->GetNext(_order); 44 | 45 | // check if not empty 46 | if (_next == tail) 47 | { 48 | _eof = true; 49 | return PipeValue.Empty; 50 | } 51 | } 52 | 53 | // go forward 54 | var node = indexService.GetNode(_next); 55 | 56 | _next = node[0]->GetNext(_order); 57 | 58 | if (_next == tail) _eof = true; 59 | 60 | var value = _returnKey ? IndexKey.ToBsonValue(node.Key) : BsonValue.Null; 61 | 62 | return new PipeValue(node.IndexNodeID, node.DataBlockID, value); 63 | } 64 | 65 | public void GetPlan(ExplainPlainBuilder builder, int deep) 66 | { 67 | builder.Add($"INDEX FULL SCAN \"{_indexDocument.Name}\" {(_order > 0 ? "ASC" : "DESC")}", deep); 68 | } 69 | 70 | public void Dispose() 71 | { 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/Indexes/IndexInEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace LiteDB.Engine; 4 | 5 | unsafe internal class IndexInEnumerator : IPipeEnumerator 6 | { 7 | private readonly Collation _collation; 8 | private readonly IndexDocument _indexDocument; 9 | private readonly bool _returnKey; 10 | 11 | private readonly BsonValue[] _values; 12 | private int _valueIndex = 0; 13 | 14 | private bool _init = false; 15 | private bool _eof = false; 16 | 17 | private IndexEqualsEnumerator? _currentIndex; 18 | 19 | public IndexInEnumerator( 20 | IEnumerable values, 21 | IndexDocument indexDocument, 22 | Collation collation, 23 | bool returnKey) 24 | { 25 | _values = values.Distinct().ToArray(); 26 | _indexDocument = indexDocument; 27 | _collation = collation; 28 | _returnKey = returnKey; 29 | } 30 | 31 | public PipeEmit Emit => new(indexNodeID: true, dataBlockID: true, value: _returnKey); 32 | 33 | public unsafe PipeValue MoveNext(PipeContext context) 34 | { 35 | if (_eof) return PipeValue.Empty; 36 | 37 | // in first run, gets head node 38 | if (_init == false) 39 | { 40 | _init = true; 41 | 42 | var first = _values[_valueIndex]; 43 | 44 | _currentIndex = new IndexEqualsEnumerator(first, _indexDocument, _collation, _returnKey); 45 | 46 | return _currentIndex.MoveNext(context); 47 | } 48 | else 49 | { 50 | var pipeValue = _currentIndex!.MoveNext(context); 51 | 52 | if (pipeValue.IsEmpty) 53 | { 54 | _valueIndex++; 55 | 56 | if (_valueIndex == _values.Length) 57 | { 58 | _eof = true; 59 | return PipeValue.Empty; 60 | } 61 | 62 | var value = _values[_valueIndex]; 63 | 64 | _currentIndex = new IndexEqualsEnumerator(value, _indexDocument, _collation, _returnKey); 65 | 66 | return _currentIndex.MoveNext(context); 67 | } 68 | 69 | return pipeValue; 70 | } 71 | } 72 | 73 | public void GetPlan(ExplainPlainBuilder builder, int deep) 74 | { 75 | builder.Add($"INDEX SEEK \"{_indexDocument.Name}\" IN ({string.Join(", ", _values.Select(x => x.ToString()))})", deep); 76 | } 77 | 78 | public void Dispose() 79 | { 80 | } 81 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/Lookup/DataLookup.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class DataLookup : IDocumentLookup 4 | { 5 | private readonly string[] _fields; 6 | 7 | public DataLookup(string[] fields) 8 | { 9 | _fields = fields; 10 | } 11 | 12 | public BsonDocument Load(PipeValue key, PipeContext context) 13 | { 14 | var result = context.DataService.ReadDocument(key.DataBlockID, _fields); 15 | 16 | if (result.Fail) throw result.Exception; 17 | 18 | return result.Value.AsDocument; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | return $"DATABLOCK {(_fields.Length == 0 ? "FULL DOCUMENT" : "FIELDS " + string.Join(", ", _fields))}"; 24 | } 25 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/Lookup/IDocumentLookup.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IDocumentLookup 4 | { 5 | BsonDocument Load(PipeValue key, PipeContext context); 6 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/Lookup/IndexLookup.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class IndexLookup : IDocumentLookup 4 | { 5 | private readonly string _field; 6 | 7 | public IndexLookup(string field) 8 | { 9 | _field = field; 10 | } 11 | 12 | public BsonDocument Load(PipeValue key, PipeContext context) 13 | { 14 | var doc = new BsonDocument { [_field] = key.Value! }; 15 | 16 | return doc; 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return $"INDEX FIELD {_field}"; 22 | } 23 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/FilterEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class FilterEnumerator : IPipeEnumerator 4 | { 5 | // dependency injections 6 | private readonly Collation _collation; 7 | 8 | private readonly IPipeEnumerator _enumerator; 9 | private readonly BsonExpression _filter; 10 | 11 | private bool _eof = false; 12 | 13 | public FilterEnumerator(BsonExpression filter, IPipeEnumerator enumerator, Collation collation) 14 | { 15 | _filter = filter; 16 | _enumerator = enumerator; 17 | _collation = collation; 18 | 19 | if (_enumerator.Emit.Value == false) throw ERR($"Filter pipe enumerator requires document from last pipe"); 20 | } 21 | 22 | public PipeEmit Emit => _enumerator.Emit; 23 | 24 | public PipeValue MoveNext(PipeContext context) 25 | { 26 | while (!_eof) 27 | { 28 | var item = _enumerator.MoveNext(context); 29 | 30 | if (item.IsEmpty) 31 | { 32 | _eof = true; 33 | } 34 | else 35 | { 36 | var result = _filter.Execute(item.Value, context.QueryParameters, _collation); 37 | 38 | if (result.IsBoolean && result.AsBoolean) 39 | { 40 | return item; 41 | } 42 | } 43 | } 44 | 45 | return PipeValue.Empty; 46 | } 47 | 48 | public void GetPlan(ExplainPlainBuilder builder, int deep) 49 | { 50 | builder.Add($"FILTER {_filter}", deep); 51 | 52 | _enumerator.GetPlan(builder, ++deep); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/IPipeEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Interface for a custom query pipe 5 | /// 6 | internal interface IPipeEnumerator : IDisposable 7 | { 8 | PipeEmit Emit { get; } 9 | 10 | PipeValue MoveNext(PipeContext context); 11 | 12 | void GetPlan(ExplainPlainBuilder builder, int deep); 13 | } 14 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/InMemoryOrderByEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class InMemoryOrderByEnumerator : IPipeEnumerator 4 | { 5 | // dependency injections 6 | private readonly Collation _collation; 7 | 8 | private readonly OrderBy _orderBy; 9 | private readonly IPipeEnumerator _enumerator; 10 | 11 | private Queue? _sortedItems; 12 | 13 | public InMemoryOrderByEnumerator( 14 | OrderBy orderBy, 15 | IPipeEnumerator enumerator, 16 | Collation collation) 17 | { 18 | _orderBy = orderBy; 19 | _enumerator = enumerator; 20 | _collation = collation; 21 | 22 | if (_enumerator.Emit.Value == false) throw ERR($"InMemoryOrderBy pipe enumerator requires document from last pipe"); 23 | } 24 | 25 | public PipeEmit Emit => new(indexNodeID: false, dataBlockID: true, value: true); 26 | 27 | public PipeValue MoveNext(PipeContext context) 28 | { 29 | if (_sortedItems is null) 30 | { 31 | var list = new List(); 32 | 33 | while (true) 34 | { 35 | var item = _enumerator.MoveNext(context); 36 | 37 | if (item.IsEmpty) break; 38 | 39 | // get sort key 40 | var key = _orderBy.Expression.Execute(item.Value, context.QueryParameters, _collation); 41 | 42 | //list.Add(new (item.DataBlockID, key, item.Document!)); 43 | throw new NotImplementedException(); 44 | } 45 | 46 | // sort list in a new enumerable 47 | var query = _orderBy.Order == Query.Ascending ? 48 | list.OrderBy(x => x.Key, _collation) : list.OrderByDescending(x => x.Key, _collation); 49 | 50 | _sortedItems = new Queue(query); 51 | } 52 | 53 | if (_sortedItems.Count > 0) 54 | { 55 | var item = _sortedItems.Dequeue(); 56 | 57 | return new PipeValue(RowID.Empty, item.DataBlockID, item.Document); 58 | } 59 | 60 | return PipeValue.Empty; 61 | } 62 | 63 | public void GetPlan(ExplainPlainBuilder builder, int deep) 64 | { 65 | builder.Add($"IN-MEMORY ORDER BY {_orderBy}", deep); 66 | 67 | _enumerator.GetPlan(builder, ++deep); 68 | } 69 | 70 | public void Dispose() 71 | { 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/LimitEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class LimitEnumerator : IPipeEnumerator 4 | { 5 | private readonly IPipeEnumerator _enumerator; 6 | 7 | private readonly int _limit; 8 | 9 | private int _count = 0; 10 | private bool _eof = false; 11 | 12 | public LimitEnumerator(int limit, IPipeEnumerator enumerator) 13 | { 14 | _limit = limit; 15 | _enumerator = enumerator; 16 | } 17 | 18 | public PipeEmit Emit => _enumerator.Emit; 19 | 20 | public PipeValue MoveNext(PipeContext context) 21 | { 22 | if (_eof) return PipeValue.Empty; 23 | 24 | var item = _enumerator.MoveNext(context); 25 | 26 | if (item.IsEmpty) 27 | { 28 | _eof = true; 29 | return PipeValue.Empty; 30 | } 31 | 32 | _count++; 33 | 34 | if (_count >= _limit) 35 | { 36 | _eof = true; 37 | } 38 | 39 | return item; 40 | } 41 | 42 | public void GetPlan(ExplainPlainBuilder builder, int deep) 43 | { 44 | builder.Add($"LIMIT {_limit}", deep); 45 | 46 | _enumerator.GetPlan(builder, ++deep); 47 | } 48 | 49 | public void Dispose() 50 | { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/LookupEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class LookupEnumerator : IPipeEnumerator 4 | { 5 | private readonly IDocumentLookup _lookup; 6 | private readonly IPipeEnumerator _enumerator; 7 | 8 | private bool _eof = false; 9 | 10 | public LookupEnumerator(IDocumentLookup lookup, IPipeEnumerator enumerator) 11 | { 12 | _lookup = lookup; 13 | _enumerator = enumerator; 14 | 15 | if (_enumerator.Emit.DataBlockID == false) throw ERR($"Lookup pipe enumerator requires DataBlockID from last pipe"); 16 | } 17 | 18 | public PipeEmit Emit => new(indexNodeID: _enumerator.Emit.IndexNodeID, dataBlockID: true, value: true); 19 | 20 | public PipeValue MoveNext(PipeContext context) 21 | { 22 | if (_eof) return PipeValue.Empty; 23 | 24 | var item = _enumerator.MoveNext(context); 25 | 26 | if (item.IsEmpty) 27 | { 28 | _eof = true; 29 | return PipeValue.Empty; 30 | } 31 | 32 | var doc = _lookup.Load(item, context); 33 | 34 | return new PipeValue(RowID.Empty, item.DataBlockID, doc); 35 | } 36 | 37 | public void GetPlan(ExplainPlainBuilder builder, int deep) 38 | { 39 | builder.Add($"LOOKUP {_lookup}", deep); 40 | 41 | _enumerator.GetPlan(builder, ++deep); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/OffsetEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class OffsetEnumerator : IPipeEnumerator 4 | { 5 | private readonly IPipeEnumerator _enumerator; 6 | 7 | private readonly int _offset; 8 | 9 | private int _count = 0; 10 | private bool _eof = false; 11 | 12 | public OffsetEnumerator(int offset, IPipeEnumerator enumerator) 13 | { 14 | _offset = offset; 15 | _enumerator = enumerator; 16 | } 17 | 18 | public PipeEmit Emit => _enumerator.Emit; 19 | 20 | public PipeValue MoveNext(PipeContext context) 21 | { 22 | if (_eof) return PipeValue.Empty; 23 | 24 | while (_count <= _offset) 25 | { 26 | var skiped = _enumerator.MoveNext(context); 27 | 28 | if (skiped.IsEmpty) 29 | { 30 | _eof = true; 31 | 32 | return PipeValue.Empty; 33 | } 34 | 35 | _count++; 36 | } 37 | 38 | var item = _enumerator.MoveNext(context); 39 | 40 | if (item.IsEmpty) 41 | { 42 | _eof = true; 43 | 44 | return PipeValue.Empty; 45 | } 46 | 47 | return item; 48 | } 49 | 50 | public void GetPlan(ExplainPlainBuilder builder, int deep) 51 | { 52 | builder.Add($"OFFSET {_offset}", deep); 53 | 54 | _enumerator.GetPlan(builder, ++deep); 55 | } 56 | 57 | public void Dispose() 58 | { 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/QueryService/PipelineEnumerators/OrderByEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class OrderByEnumerator : IPipeEnumerator 4 | { 5 | // dependency injections 6 | private readonly ISortOperation _sorter; 7 | 8 | private readonly OrderBy _orderBy; 9 | private readonly IPipeEnumerator _enumerator; 10 | private bool _init; 11 | 12 | public OrderByEnumerator( 13 | OrderBy orderBy, 14 | IPipeEnumerator enumerator, 15 | ISortService sortService) 16 | { 17 | _enumerator = enumerator; 18 | 19 | if (_enumerator.Emit.DataBlockID == false) throw ERR($"OrderBy pipe enumerator requires DataBlockID from last pipe"); 20 | if (_enumerator.Emit.Value == false) throw ERR($"OrderBy pipe enumerator requires Document from last pipe"); 21 | 22 | _orderBy = orderBy; 23 | _sorter = sortService.CreateSort(orderBy); 24 | } 25 | 26 | public PipeEmit Emit => new(indexNodeID: false, dataBlockID: true, value: false); 27 | 28 | public PipeValue MoveNext(PipeContext context) 29 | { 30 | if(_init == false) 31 | { 32 | // consume all _enumerator and get ready for new enumerator: _sorter 33 | _sorter.InsertData(_enumerator, context); 34 | _init = true; 35 | } 36 | 37 | // get next sorted item (returns Empty when EOF) 38 | var item = _sorter.MoveNext(); 39 | 40 | return new PipeValue(RowID.Empty, item.DataBlockID); 41 | } 42 | 43 | public void GetPlan(ExplainPlainBuilder builder, int deep) 44 | { 45 | builder.Add($"ORDER BY {_orderBy}", deep); 46 | 47 | _enumerator.GetPlan(builder, ++deep); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | // dispose/release all used containers 53 | _sorter.Dispose(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Services/SortService/SortService.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | [AutoInterface(typeof(IDisposable))] 4 | internal class SortService : ISortService 5 | { 6 | private readonly IServicesFactory _factory; 7 | private readonly IStreamFactory _sortStreamFactory; 8 | 9 | private readonly ConcurrentQueue _availableContainersID = new(); 10 | private readonly ConcurrentQueue _streamPool = new(); 11 | private int _nextContainerID = -1; 12 | 13 | public SortService( 14 | IStreamFactory sortStreamFactory, 15 | IServicesFactory factory) 16 | { 17 | _sortStreamFactory = sortStreamFactory; 18 | _factory = factory; 19 | } 20 | 21 | public ISortOperation CreateSort(OrderBy orderBy) 22 | { 23 | var sorter = _factory.CreateSortOperation(orderBy); 24 | 25 | return sorter; 26 | } 27 | 28 | public int GetAvailableContainerID() 29 | { 30 | if (_availableContainersID.TryDequeue(out var containerID)) 31 | { 32 | return containerID; 33 | } 34 | 35 | return Interlocked.Increment(ref _nextContainerID); 36 | } 37 | 38 | public Stream RentSortStream() 39 | { 40 | if (!_streamPool.TryDequeue(out var stream)) 41 | { 42 | stream = _sortStreamFactory.GetStream(true, FileOptions.None); // parameters are not used for sort stream factory 43 | } 44 | 45 | return stream; 46 | } 47 | 48 | public void ReleaseSortStream(Stream stream) 49 | { 50 | _streamPool.Enqueue(stream); 51 | } 52 | 53 | public void Dispose() 54 | { 55 | foreach(var stream in _streamPool) 56 | { 57 | stream.Dispose(); 58 | } 59 | 60 | if (_streamPool.Count > 0) 61 | { 62 | _sortStreamFactory.Delete(); 63 | } 64 | 65 | // clear service states 66 | _availableContainersID.Clear(); 67 | _streamPool.Clear(); 68 | _nextContainerID = -1; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Commands/SqlParser.CreateCollection.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Internal class to parse and execute sql-like commands 5 | /// 6 | internal partial class SqlParser 7 | { 8 | /// 9 | /// create_collection:: 10 | /// "CREATE" _ "COLLECTION" _ user_collection 11 | /// 12 | private IEngineStatement ParseCreateCollection() 13 | { 14 | _tokenizer.ReadToken().Expect("COLLECTION"); // CREATE token already readed 15 | 16 | // create collection name 17 | if (!this.TryParseDocumentStore(out var store)) throw ERR_UNEXPECTED_TOKEN(_tokenizer.Current, "{document_store}"); 18 | 19 | // expect end of statement 20 | _tokenizer.ReadToken().Expect(TokenType.EOF, TokenType.SemiColon); 21 | 22 | return new CreateCollectionStatement(store.Name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Commands/SqlParser.CreateIndex.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Internal class to parse and execute sql-like commands 5 | /// 6 | internal partial class SqlParser 7 | { 8 | /// 9 | /// create_index:: 10 | /// "CREATE" [_ "UNIQUE"] _ "INDEX" _ word _ "ON" _ user_collection. "(" . expr_single. ")" 11 | /// 12 | private IEngineStatement ParseCreateIndex() 13 | { 14 | var token = _tokenizer.ReadToken().Expect(TokenType.Word); 15 | var unique = token.Value.Eq("UNIQUE"); 16 | 17 | if (unique) 18 | { 19 | _tokenizer.ReadToken().Expect("INDEX"); 20 | } 21 | else 22 | { 23 | token.Expect("INDEX"); 24 | } 25 | 26 | var indexName = _tokenizer.ReadToken().Expect(TokenType.Word).Value; // get index name 27 | 28 | _tokenizer.ReadToken().Expect("ON"); 29 | 30 | // get collection name 31 | if (!this.TryParseDocumentStore(out var store)) throw ERR_UNEXPECTED_TOKEN(_tokenizer.Current, "{document_store}"); 32 | 33 | // read ( 34 | _tokenizer.ReadToken().Expect(TokenType.OpenParenthesis); 35 | 36 | // read index expression 37 | var expr = BsonExpression.Create(_tokenizer, false); 38 | 39 | // read ) 40 | _tokenizer.ReadToken().Expect(TokenType.CloseParenthesis); 41 | 42 | // expect end of statement 43 | _tokenizer.ReadToken().Expect(TokenType.EOF, TokenType.SemiColon); 44 | 45 | return new CreateIndexStatement(store.Name, indexName, expr, unique); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Commands/SqlParser.Insert.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Text.Json.Nodes; 3 | using System.Text.Json; 4 | 5 | namespace LiteDB.Engine; 6 | 7 | /// 8 | /// Internal class to parse and execute sql-like commands 9 | /// 10 | internal partial class SqlParser 11 | { 12 | /// 13 | /// insert_statement:: 14 | /// "INSERT" _ "INTO" _ document_store. [":" auto_id] 15 | /// _ "VALUES" _ (json_document | json_array | sub_query | expr_parameter) 16 | /// 17 | private IEngineStatement ParseInsert() 18 | { 19 | _tokenizer.ReadToken().Expect("INSERT"); 20 | _tokenizer.ReadToken().Expect("INTO"); 21 | 22 | if (!this.TryParseDocumentStore(out var store)) throw ERR_UNEXPECTED_TOKEN(_tokenizer.Current, "{document_store}"); 23 | 24 | TryParseWithAutoId(out var autoId); 25 | 26 | _tokenizer.ReadToken().Expect("VALUES"); 27 | 28 | var ahead = _tokenizer.LookAhead(); 29 | InsertStatement statement; 30 | 31 | if (ahead.Type == TokenType.At) // @0 - is expression parameter 32 | { 33 | var docExpr = BsonExpression.Create(_tokenizer, false); 34 | 35 | statement = new InsertStatement(store, docExpr, autoId); 36 | } 37 | else if (ahead.Type == TokenType.OpenBrace) // { new json document 38 | { 39 | var doc = JsonReaderStatic.ReadDocument(_tokenizer); // read full json_document 40 | 41 | statement = new InsertStatement(store, doc, autoId); 42 | } 43 | else if (ahead.Type == TokenType.OpenBracket) // [ new json array 44 | { 45 | var array = JsonReaderStatic.ReadArray(_tokenizer); // read full json_array 46 | 47 | statement = new InsertStatement(store, array, autoId); 48 | } 49 | else if (ahead.Type == TokenType.OpenParenthesis) // ( new sub_query 50 | { 51 | throw new NotImplementedException("sub_query"); 52 | } 53 | else 54 | { 55 | throw ERR_UNEXPECTED_TOKEN(ahead, "Expression parameter, JsonDocument or JsonArray"); 56 | } 57 | 58 | // expect end of statement 59 | _tokenizer.ReadToken().Expect(TokenType.EOF, TokenType.SemiColon); 60 | 61 | return statement; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Core/SqlParser.AutoId.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal partial class SqlParser 4 | { 5 | /// 6 | /// auto_id:: 7 | /// | "GUID" 8 | /// | "INT" 9 | /// | "LONG" 10 | /// | "OBJECTID" 11 | /// 12 | private bool TryParseWithAutoId(out BsonAutoId autoId) 13 | { 14 | var with = _tokenizer.LookAhead(); 15 | 16 | if (with.Type == TokenType.Colon) 17 | { 18 | _tokenizer.ReadToken(); 19 | 20 | var type = _tokenizer.ReadToken().Expect(TokenType.Word); 21 | 22 | if (type.Value.Eq("GUID")) 23 | autoId = BsonAutoId.Guid; 24 | else if (type.Value.Eq("INT")) 25 | autoId = BsonAutoId.Int32; 26 | else if (type.Value.Eq("LONG")) 27 | autoId = BsonAutoId.Int64; 28 | else if (type.Value.Eq("OBJECTID")) 29 | autoId = BsonAutoId.ObjectId; 30 | else 31 | throw ERR_UNEXPECTED_TOKEN(type, "GUID, INT, LONG, OBJECTID"); 32 | 33 | return true; 34 | } 35 | 36 | autoId = BsonAutoId.Int32; 37 | 38 | return false; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Core/SqlParser.DocumentStore.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal partial class SqlParser 4 | { 5 | /// 6 | /// document_store:: 7 | /// | user_collection 8 | /// | virtual_collection 9 | /// 10 | /// user_collection:: 11 | /// char word 12 | /// 13 | /// virtual_collection:: 14 | /// "$" word [ collection_parameters ] 15 | /// 16 | private bool TryParseDocumentStore(out IDocumentStore store) 17 | { 18 | var ahead = _tokenizer.LookAhead(); 19 | 20 | if (ahead.Type == TokenType.Word) // user_collection 21 | { 22 | var token = _tokenizer.ReadToken(); // read "collection_name"; 23 | 24 | store = new UserCollectionStore(token.Value); 25 | 26 | return true; 27 | } 28 | else if (ahead.Type != TokenType.Dollar) 29 | { 30 | _tokenizer.ReadToken(); // read "$"; 31 | 32 | var token = _tokenizer.ReadToken().Expect(TokenType.Word); // read "collection-name" 33 | 34 | if (TryParseParameters(out var parameters)) 35 | { 36 | //TODO: verificar metodos 37 | } 38 | 39 | 40 | throw new NotImplementedException(); 41 | 42 | //return true; 43 | } 44 | 45 | store = default; 46 | return false; 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Core/SqlParser.ListOfExpressions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal partial class SqlParser 4 | { 5 | /// 6 | /// list_of_expressions:: 7 | /// expr_single [ . "," . expr_single ]* 8 | /// 9 | private IEnumerable ParseListOfExpressions() 10 | { 11 | while (true) 12 | { 13 | var expr = BsonExpression.Create(_tokenizer, false); 14 | 15 | yield return expr; 16 | 17 | var next = _tokenizer.LookAhead(); 18 | 19 | if (next.Type == TokenType.Comma) 20 | { 21 | _tokenizer.ReadToken(); 22 | } 23 | else 24 | { 25 | yield break; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/Core/SqlParser.Parameters.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal partial class SqlParser 4 | { 5 | /// 6 | /// collection_parameters:: 7 | /// "(" [ . json_value [ . "," . json_value ] ] . ")" 8 | /// 9 | private bool TryParseParameters(out IReadOnlyList parameters) 10 | { 11 | var ahead = _tokenizer.LookAhead(); 12 | 13 | if (ahead.Type != TokenType.OpenParenthesis) 14 | { 15 | parameters = Array.Empty(); 16 | return false; 17 | } 18 | 19 | var token = _tokenizer.ReadToken(); // read "(" 20 | 21 | var result = new List(); 22 | 23 | while (token.Type != TokenType.CloseParenthesis) 24 | { 25 | var value = JsonReaderStatic.ReadValue(_tokenizer); 26 | 27 | result.Add(value); 28 | 29 | token = _tokenizer.ReadToken(); // read , "," or ")" 30 | 31 | if (token.Type == TokenType.Comma) 32 | { 33 | token = _tokenizer.ReadToken(); 34 | } 35 | } 36 | 37 | parameters = result; 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/SqlParser/SqlParser.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Internal class to parse and execute sql-like commands 5 | /// 6 | internal partial class SqlParser 7 | { 8 | private Tokenizer _tokenizer; 9 | private Collation _collation; 10 | private int _nameIndex = 0; // used for columns with no names will generate "expr1", "expr2" 11 | 12 | public SqlParser(Tokenizer tokenizer, Collation collation) 13 | { 14 | _tokenizer = tokenizer; 15 | _collation = collation; 16 | } 17 | 18 | public IEngineStatement ParseStatement() 19 | { 20 | var ahead = _tokenizer.LookAhead().Expect(TokenType.Word); 21 | 22 | if (ahead.Value.Eq("CREATE")) 23 | { 24 | _tokenizer.ReadToken(); // read CREATE 25 | ahead = _tokenizer.LookAhead(); 26 | 27 | if (ahead.Value.Eq("COLLECTION")) return this.ParseCreateCollection(); 28 | if (ahead.Value.Eq("INDEX")) return this.ParseCreateIndex(); 29 | 30 | throw ERR_UNEXPECTED_TOKEN(ahead); 31 | } 32 | 33 | 34 | if (ahead.Value.Eq("SELECT") || ahead.Value.Eq("EXPLAIN")) return this.ParseSelect(); 35 | 36 | if (ahead.Value.Eq("INSERT")) return this.ParseInsert(); 37 | 38 | //if (ahead.Value.Eq("DELETE")) return this.ParseDelete(); 39 | //if (ahead.Value.Eq("UPDATE")) return this.ParseUpdate(); 40 | //if (ahead.Value.Eq("DROP")) return this.ParseDrop(); 41 | //if (ahead.Value.Eq("RENAME")) return this.ParseRename(); 42 | //if (ahead.Value.Eq("CREATE")) return this.ParseCreate(); 43 | // 44 | //if (ahead.Value.Eq("CHECKPOINT")) return this.ParseCheckpoint(); 45 | //if (ahead.Value.Eq("REBUILD")) return this.ParseRebuild(); 46 | // 47 | //if (ahead.Value.Eq("BEGIN")) return this.ParseBegin(); 48 | //if (ahead.Value.Eq("ROLLBACK")) return this.ParseRollback(); 49 | //if (ahead.Value.Eq("COMMIT")) return this.ParseCommit(); 50 | // 51 | //if (ahead.Value.Eq("PRAGMA")) return this.ParsePragma(); 52 | 53 | throw ERR_UNEXPECTED_TOKEN(ahead); 54 | } 55 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/BsonDataReader/BsonDataReaderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Implement some Enumerable methods to IBsonDataReader 5 | /// 6 | public static class BsonDataReaderExtensions 7 | { 8 | public static async IAsyncEnumerable ToEnumerableAsync(this IDataReader reader) 9 | { 10 | try 11 | { 12 | while (await reader.ReadAsync()) 13 | { 14 | yield return reader.Current; 15 | } 16 | } 17 | finally 18 | { 19 | reader.Dispose(); 20 | } 21 | } 22 | 23 | //TODO: tenho que consumier o async enumerable pra gerar isso 24 | //public static ValueTask ToArrayAsync(this IBsonDataReader reader) => ToEnumerableAsync(reader); 25 | // 26 | //public static IList ToListAsync(this IBsonDataReader reader) => ToEnumerable(reader).ToList(); 27 | // 28 | //public static BsonValue FirstAsync(this IBsonDataReader reader) => ToEnumerable(reader).First(); 29 | // 30 | //public static BsonValue FirstOrDefaultAsync(this IBsonDataReader reader) => ToEnumerable(reader).FirstOrDefault(); 31 | // 32 | //public static BsonValue SingleAsync(this IBsonDataReader reader) => ToEnumerable(reader).Single(); 33 | // 34 | //public static BsonValue SingleOrDefaultAsync(this IBsonDataReader reader) => ToEnumerable(reader).SingleOrDefault(); 35 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/BsonDataReader/BsonScalarReader.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Implement a data reader for a single value 5 | /// 6 | public class BsonScalarReader : IDataReader 7 | { 8 | private readonly string _collection; 9 | private readonly BsonValue _value; 10 | private int _current = -1; 11 | 12 | /// 13 | /// Initialize data reader with created cursor 14 | /// 15 | internal BsonScalarReader(string collection, BsonValue value) 16 | { 17 | _collection = collection; 18 | _value = value; 19 | } 20 | 21 | /// 22 | /// Return current value 23 | /// 24 | public BsonValue Current => 25 | _value is BsonArray array ? 26 | (_current >= 0 ? array[_current] : BsonValue.Null) : 27 | _value; 28 | 29 | /// 30 | /// Return collection name 31 | /// 32 | public string Collection => _collection; 33 | 34 | /// 35 | /// Move cursor to next result. Returns true if read was possible 36 | /// 37 | public ValueTask ReadAsync() 38 | { 39 | if (_current == -1) 40 | { 41 | _current = 0; 42 | 43 | return new ValueTask(true); 44 | } 45 | else if (_value is BsonArray array) 46 | { 47 | _current++; 48 | 49 | if (_current >= array.Count) 50 | { 51 | return new ValueTask(false); 52 | } 53 | 54 | return new ValueTask(true); 55 | } 56 | else 57 | { 58 | return new ValueTask(false); 59 | } 60 | } 61 | 62 | public BsonValue this[string field] => _value.AsDocument[field]; 63 | 64 | public void Dispose() 65 | { 66 | } 67 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/BsonDataReader/IDataReader.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | public interface IDataReader : IDisposable 4 | { 5 | BsonValue this[string field] { get; } 6 | string Collection { get; } 7 | BsonValue Current { get; } 8 | ValueTask ReadAsync(); 9 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/CheckpointStatement.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class CheckpointStatement : IEngineStatement 4 | { 5 | public EngineStatementType StatementType => EngineStatementType.Checkpoint; 6 | 7 | public CheckpointStatement() 8 | { 9 | } 10 | 11 | public async ValueTask ExecuteAsync(IServicesFactory factory, BsonDocument parameters) 12 | { 13 | using var _pc = PERF_COUNTER(37, nameof(ExecuteAsync), nameof(CheckpointStatement)); 14 | 15 | var lockService = factory.LockService; 16 | var logService = factory.LogService; 17 | 18 | // checkpoint require exclusive lock (no readers/writers) 19 | await lockService.EnterExclusiveAsync(); 20 | 21 | // do checkpoint and returns how many pages was overrided 22 | var result = await logService.CheckpointAsync(false, /* true*/ false); 23 | 24 | // release exclusive 25 | lockService.ExitExclusive(); 26 | 27 | return result; 28 | } 29 | 30 | public ValueTask ExecuteReaderAsync(IServicesFactory factory, BsonDocument parameters) => throw new NotSupportedException(); 31 | } 32 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/Interfaces/EngineStatementType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal enum EngineStatementType 4 | { 5 | // dml 6 | Insert, 7 | Update, 8 | Delete, 9 | 10 | // query 11 | Select, 12 | 13 | // collections 14 | CreateCollection, 15 | RenameCollection, 16 | DropCollection, 17 | 18 | // indexes 19 | CreateIndex, 20 | DropIndex, 21 | 22 | // tools 23 | Checkpoint, 24 | Rebuild 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/Interfaces/IEngineStatement.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal interface IEngineStatement 4 | { 5 | EngineStatementType StatementType { get; } 6 | ValueTask ExecuteAsync(IServicesFactory factory, BsonDocument parameters); 7 | ValueTask ExecuteReaderAsync(IServicesFactory factory, BsonDocument parameters); 8 | } 9 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Statements/SelectStatement.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class SelectStatement : IEngineStatement 4 | { 5 | private readonly Query _query; 6 | private readonly bool _explain; 7 | private readonly int _fetchSize; 8 | 9 | public EngineStatementType StatementType => EngineStatementType.Select; 10 | 11 | #region Ctor 12 | 13 | public SelectStatement(Query query) : 14 | this(query, 1000, false) 15 | { 16 | } 17 | 18 | public SelectStatement(Query query, bool explain) : 19 | this(query, 1000, explain) 20 | { 21 | } 22 | 23 | public SelectStatement(Query query, int fetchSize) : 24 | this (query, fetchSize, false) 25 | { 26 | } 27 | 28 | public SelectStatement(Query query, int fetchSize, bool explain) 29 | { 30 | _query = query; 31 | _fetchSize = fetchSize; 32 | _explain = explain; 33 | } 34 | 35 | #endregion 36 | 37 | public ValueTask ExecuteReaderAsync(IServicesFactory factory, BsonDocument parameters) 38 | { 39 | using var _pc = PERF_COUNTER(31, nameof(ExecuteReaderAsync), nameof(SelectStatement)); 40 | 41 | // get dependencies 42 | var walIndexService = factory.WalIndexService; 43 | var queryService = factory.QueryService; 44 | 45 | // get next read version without open a new transaction 46 | var readVersion = walIndexService.GetNextReadVersion(); 47 | 48 | // create cursor after query optimizer and create enumerator pipeline 49 | var cursor = queryService.CreateCursor(_query, parameters, readVersion); 50 | 51 | // create concrete class to reader cursor 52 | IDataReader reader = _explain ? 53 | new BsonScalarReader(cursor.Query.Collection, cursor.GetExplainPlan()) : // for explain plain 54 | factory.CreateDataReader(cursor, _fetchSize, factory); 55 | 56 | return ValueTask.FromResult(reader); 57 | } 58 | 59 | public ValueTask ExecuteAsync(IServicesFactory factory, BsonDocument parameters) => throw new NotSupportedException(); 60 | } 61 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Core/RowID.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct RowID : IEquatable, IIsEmpty 4 | { 5 | public uint PageID; // 4 6 | public ushort Index; // 2 7 | public ushort Reserved; // 2 8 | 9 | public static readonly RowID Empty = new () { PageID = uint.MaxValue, Index = ushort.MaxValue }; 10 | 11 | public bool IsEmpty => this.PageID == uint.MaxValue && this.Index == ushort.MaxValue; 12 | 13 | public RowID() 14 | { 15 | } 16 | 17 | public RowID(uint pageID, ushort index) 18 | { 19 | this.PageID = pageID; 20 | this.Index = index; 21 | } 22 | 23 | public bool Equals(RowID other) 24 | { 25 | return this.PageID == other.PageID && this.Index == other.Index; 26 | } 27 | public override bool Equals(object? other) => this.Equals((RowID)other!); 28 | 29 | public static bool operator ==(RowID left, RowID right) 30 | { 31 | return left.PageID == right.PageID && left.Index == right.Index; 32 | } 33 | 34 | public static bool operator !=(RowID left, RowID right) 35 | { 36 | return !(left == right); 37 | } 38 | 39 | public override int GetHashCode() => HashCode.Combine(PageID, Index); 40 | 41 | public override string ToString() 42 | { 43 | return IsEmpty ? "" : $"{PageID:0000}:{Index:00}"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Core/Sequence.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct Sequence : IIsEmpty 4 | { 5 | public int LastInt; 6 | public long LastLong; 7 | 8 | public bool IsEmpty => this.LastInt == int.MaxValue && this.LastLong == long.MaxValue; 9 | 10 | public Sequence() 11 | { 12 | this.Reset(); 13 | } 14 | 15 | public void Reset() 16 | { 17 | this.LastInt = int.MaxValue; 18 | this.LastLong = long.MaxValue; 19 | } 20 | 21 | public override string ToString() => Dump.Object(this); 22 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Enums/CheckpointActionType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal enum CheckpointActionType : byte 4 | { 5 | CopyToDataFile = 0, 6 | CopyToTempFile = 1, 7 | ClearPage = 2 8 | } 9 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Enums/EngineState.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// All engine state 5 | /// 6 | public enum EngineState 7 | { 8 | /// 9 | /// Initial state - all services are disposed no touch on file 10 | /// 11 | Close, 12 | 13 | /// 14 | /// Database are in recovery process 15 | /// 16 | Recovery, 17 | 18 | /// 19 | /// Database are created and initialized ok. Any recovery already made. 20 | /// Ready to use 21 | /// 22 | Open, 23 | 24 | /// 25 | /// Database are in shutdown process - called by user or Critical exception. 26 | /// No one can use until back to initial state: Closed 27 | /// 28 | Shutdown, 29 | 30 | /// 31 | /// Database are in rebuild process 32 | /// 33 | Rebuild 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Enums/PageType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal enum PageType : byte 4 | { 5 | Empty = 0, 6 | AllocationMap = 1, 7 | Index = 2, 8 | Data = 3, 9 | Unknown = 255 10 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Extend/ExtendLocation.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct ExtendLocation : IIsEmpty 4 | { 5 | public readonly int AllocationMapID; // 4 6 | public readonly int ExtendIndex; // 4 7 | 8 | public bool IsEmpty => this.AllocationMapID == -1 && this.ExtendIndex == -1; 9 | 10 | public int ExtendID => this.IsEmpty ? -1 : (this.AllocationMapID * AM_EXTEND_COUNT) + this.ExtendIndex; 11 | 12 | public uint FirstPageID => (uint)(this.AllocationMapID * AM_PAGE_STEP + this.ExtendIndex * AM_EXTEND_SIZE + 1); 13 | 14 | public ExtendLocation() 15 | { 16 | this.AllocationMapID = 0; 17 | this.ExtendIndex = 0; 18 | } 19 | 20 | public ExtendLocation(int allocationMapID, int extendIndex) 21 | { 22 | this.AllocationMapID = allocationMapID; 23 | this.ExtendIndex = extendIndex; 24 | 25 | ENSURE(extendIndex < AM_EXTEND_COUNT, new { self = this }); 26 | } 27 | 28 | public ExtendLocation(int extendID) 29 | { 30 | this.AllocationMapID = extendID / AM_EXTEND_COUNT; 31 | this.ExtendIndex = extendID % AM_EXTEND_COUNT; 32 | } 33 | 34 | public override string ToString() => Dump.Object(this); 35 | } 36 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Extend/ExtendPageValue.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal enum ExtendPageValue : byte 4 | { 5 | Empty = 0, // 000 6 | Data = 1, // 001 7 | Index = 2, // 010 8 | // 3-6 reserved 9 | Full = 7, // 111 10 | 11 | NoChange = 255 12 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Log/CheckpointAction.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct CheckpointAction 4 | { 5 | public CheckpointActionType Action; 6 | public uint PositionID; 7 | public uint TargetPositionID; // used only in CopyToDataFile and CopyToTemp (MaxValue for ClearPage) 8 | public bool MustClear; // clear page PositionID 9 | 10 | public override string ToString() 11 | { 12 | return Dump.Object(new { Action, PositionID = Dump.PageID(PositionID), TargetPositionID = Dump.PageID(TargetPositionID), MustClear }); 13 | } 14 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Log/LogPageHeader.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct LogPageHeader 4 | { 5 | public uint PositionID; 6 | public uint PageID; 7 | public int TransactionID; 8 | public bool IsConfirmed; 9 | } 10 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Log/LogPosition.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct LogPosition : IEqualityComparer 4 | { 5 | public uint PositionID; 6 | public uint PageID; 7 | public uint PhysicalID; 8 | public bool IsConfirmed; 9 | 10 | public bool Equals(LogPosition x, LogPosition y) 11 | { 12 | return x.PositionID == y.PositionID; 13 | } 14 | 15 | public int GetHashCode(LogPosition obj) => HashCode.Combine(PositionID, PageID, PhysicalID, IsConfirmed); 16 | 17 | public override string ToString() 18 | { 19 | return Dump.Object(new { PhysicalID = Dump.PageID(PhysicalID), PositionID = Dump.PageID(PositionID), PageID = Dump.PageID(PageID), IsConfirmed }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/Cursor.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class Cursor : IDisposable, IIsEmpty 4 | { 5 | public int CursorID { get; } 6 | 7 | public Query Query { get; } 8 | public BsonDocument Parameters { get; } 9 | public int ReadVersion { get; } 10 | public IPipeEnumerator Enumerator { get; } 11 | 12 | public int FetchCount { get; set; } = 0; 13 | public int Offset { get; set; } = 0; 14 | public bool IsRunning { get; set; } = false; 15 | 16 | public DateTime Start { get; } = DateTime.UtcNow; 17 | public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero; 18 | 19 | public BsonDocument? NextDocument { get; set; } 20 | 21 | public bool IsEmpty => this.CursorID == 0; 22 | 23 | public static Cursor Empty = new Cursor(); 24 | 25 | public Cursor() 26 | { 27 | this.CursorID = 0; 28 | this.Parameters = BsonDocument.Empty; 29 | this.ReadVersion = 0; 30 | this.Enumerator = null; 31 | } 32 | 33 | public Cursor(Query query, BsonDocument parameters, int readVersion, IPipeEnumerator enumerator) 34 | { 35 | this.CursorID = Randomizer.Next(1000, int.MaxValue); // create a random cursorID 36 | this.Query = query; 37 | this.Parameters = parameters; 38 | this.ReadVersion = readVersion; 39 | this.Enumerator = enumerator; 40 | } 41 | 42 | public BsonArray GetExplainPlan() 43 | { 44 | if (this.Enumerator is null) return BsonArray.Empty; 45 | 46 | var explain = new ExplainPlainBuilder(); 47 | 48 | this.Enumerator.GetPlan(explain, 0); 49 | 50 | return explain.ToBsonArray(); 51 | } 52 | 53 | public void Dispose() 54 | { 55 | this.Enumerator.Dispose(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/ExplainPlainBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal class ExplainPlainBuilder 4 | { 5 | private List<(string info, int deep)> _infos = new(); 6 | 7 | public void Add(string info, int deep) 8 | { 9 | _infos.Add((info, deep)); 10 | } 11 | 12 | public BsonArray ToBsonArray() 13 | { 14 | var deeps = _infos.Max(x => x.deep); 15 | var lines = _infos 16 | .OrderByDescending(x => x.deep) 17 | .Select(x => "".PadRight(deeps - x.deep, ' ') + "> " + x.info) 18 | .Select(x => new BsonString(x)) 19 | .ToArray(); 20 | 21 | return BsonArray.FromArray(lines); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/Into.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public struct Into : IIsEmpty 4 | { 5 | public readonly static Into Empty = new ("", BsonAutoId.ObjectId); 6 | 7 | public readonly string Collection; 8 | public readonly BsonAutoId AutoId; 9 | 10 | public Into(string store, BsonAutoId autoId) 11 | { 12 | this.Collection = store; 13 | this.AutoId = autoId; 14 | } 15 | 16 | public bool IsEmpty => string.IsNullOrEmpty(Collection); 17 | 18 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/OrderBy.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Represent an OrderBy definition 5 | /// 6 | public struct OrderBy : IIsEmpty 7 | { 8 | public static OrderBy Empty = new(BsonExpression.Empty, 0); 9 | 10 | public BsonExpression Expression { get; } 11 | 12 | public int Order { get; set; } 13 | 14 | public bool IsEmpty => this.Expression.IsEmpty && this.Order == 0; 15 | 16 | public OrderBy(BsonExpression expression, int order) 17 | { 18 | this.Expression = expression; 19 | this.Order = order; 20 | } 21 | 22 | public override string ToString() => IsEmpty ? "" : 23 | $"{Expression} {(Order > 0 ? "ASC" : "DESC")}"; 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/PipeContext.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct PipeContext 4 | { 5 | public readonly IDataService DataService; 6 | public readonly IIndexService IndexService; 7 | public readonly BsonDocument QueryParameters; 8 | 9 | public PipeContext(IDataService dataService, IIndexService indexService, BsonDocument queryParameters) 10 | { 11 | this.DataService = dataService; 12 | this.IndexService = indexService; 13 | this.QueryParameters = queryParameters; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/PipeEmit.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Structure to define enumerators emit after pipe (indexBlockID, dataBlockID and/or value) 5 | /// 6 | internal readonly struct PipeEmit 7 | { 8 | public readonly bool IndexNodeID; 9 | public readonly bool DataBlockID; 10 | public readonly bool Value; 11 | 12 | public PipeEmit(bool indexNodeID, bool dataBlockID, bool value) 13 | { 14 | this.IndexNodeID = indexNodeID; 15 | this.DataBlockID = dataBlockID; 16 | this.Value = value; 17 | } 18 | 19 | public override string ToString() => Dump.Object(this); 20 | } 21 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/PipeValue.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal readonly struct PipeValue : IIsEmpty 4 | { 5 | public readonly RowID IndexNodeID; 6 | public readonly RowID DataBlockID; 7 | public readonly BsonValue Value; 8 | 9 | public static readonly PipeValue Empty = new(); 10 | 11 | public readonly bool IsEmpty => this.IndexNodeID.IsEmpty && this.DataBlockID.IsEmpty && this.Value.IsNull; 12 | 13 | public PipeValue(RowID indexNodeID, RowID dataBlockID) 14 | { 15 | this.IndexNodeID = indexNodeID; 16 | this.DataBlockID = dataBlockID; 17 | this.Value = BsonValue.Null; 18 | } 19 | 20 | public PipeValue(RowID indexNodeID, RowID dataBlockID, BsonValue value) 21 | { 22 | this.IndexNodeID = indexNodeID; 23 | this.DataBlockID = dataBlockID; 24 | this.Value = value; 25 | } 26 | 27 | public PipeValue(BsonValue value) 28 | { 29 | this.IndexNodeID = RowID.Empty; 30 | this.DataBlockID = RowID.Empty; 31 | this.Value = value; 32 | } 33 | 34 | public PipeValue() 35 | { 36 | this.IndexNodeID = RowID.Empty; 37 | this.DataBlockID = RowID.Empty; 38 | this.Value = BsonValue.Null; 39 | } 40 | 41 | public override string ToString() => Dump.Object(this); 42 | } 43 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/Query.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// 5 | public class Query 6 | { 7 | #region Constants 8 | 9 | /// 10 | /// Indicate when a query must execute in ascending order 11 | /// 12 | public const int Ascending = 1; 13 | 14 | /// 15 | /// Indicate when a query must execute in descending order 16 | /// 17 | public const int Descending = -1; 18 | 19 | #endregion 20 | 21 | public required string Collection { get; init; } 22 | public SelectFields Select { get; init; } = SelectFields.Root; 23 | public bool Distinct { get; init; } = false; 24 | public Into Into { get; init; } = Into.Empty; 25 | public IReadOnlyList Includes { get; init; } = Array.Empty(); 26 | public BsonExpression Where { get; init; } = BsonExpression.Empty; 27 | public BsonExpression GroupBy { get; set; } = BsonExpression.Empty; 28 | public BsonExpression Having { get; init; } = BsonExpression.Empty; 29 | public OrderBy OrderBy { get; init; } = OrderBy.Empty; 30 | public int Offset { get; init; } = 0; 31 | public int Limit { get; init; } = int.MaxValue; 32 | 33 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/Resultset.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public struct Resultset : IDisposable 4 | { 5 | public int From; 6 | public int To; 7 | public int DocumentCount; 8 | public bool HasMore; 9 | public readonly SharedArray Results; 10 | 11 | public Resultset(int fetchSize) 12 | { 13 | this.Results = SharedArray.Rent(fetchSize); 14 | } 15 | 16 | public override string ToString() => Dump.Object(this); 17 | 18 | public void Dispose() 19 | { 20 | this.Results.Dispose(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/SelectField.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public readonly struct SelectField 4 | { 5 | private static Dictionary> _aggregateMethods = new(StringComparer.OrdinalIgnoreCase) 6 | { 7 | ["COUNT"] = (e) => new CountFunc(e), 8 | ["MIN"] = (e) => new MinFunc(e), 9 | ["MAX"] = (e) => new MaxFunc(e), 10 | ["FIRST"] = (e) => new FirstFunc(e), 11 | ["LAST"] = (e) => new LastFunc(e), 12 | ["AVG"] = (e) => new AvgFunc(e), 13 | ["SUM"] = (e) => new SumFunc(e), 14 | ["ANY"] = (e) => new AnyFunc(e), 15 | ["ARRAY"] = (e) => new ArrayFunc(e) 16 | }; 17 | 18 | public readonly string Name; 19 | public readonly bool Hidden; 20 | public readonly BsonExpression Expression; 21 | 22 | public SelectField(string name, bool hidden, BsonExpression expression) 23 | { 24 | this.Name = name; 25 | this.Hidden = hidden; 26 | this.Expression = expression; 27 | } 28 | 29 | public bool IsAggregate => 30 | this.Expression is CallBsonExpression call && 31 | _aggregateMethods.ContainsKey(call.Method.Name); 32 | 33 | public IAggregateFunc CreateAggregateFunc() 34 | { 35 | var call = this.Expression as CallBsonExpression; 36 | 37 | var fn = _aggregateMethods[call!.Method.Name]; 38 | 39 | // get first parameter from method call 40 | var expr = call.Children.FirstOrDefault()!; 41 | 42 | return fn(expr); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Query/SelectFields.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | public readonly struct SelectFields 4 | { 5 | /// 6 | /// SELECT * 7 | /// 8 | public static readonly SelectFields Root = new (BsonExpression.Root); 9 | 10 | /// 11 | /// SELECT $._id 12 | /// 13 | public static readonly SelectFields Id = new(new SelectField[] { new SelectField("_id", false, BsonExpression.Id) }); 14 | 15 | // fields 16 | public readonly BsonExpression SingleExpression; 17 | public readonly IReadOnlyList Fields; 18 | 19 | // properties 20 | 21 | /// 22 | /// Indicate this query will return root/full document. Means "SELECT *" 23 | /// 24 | public bool IsRoot => this.SingleExpression == BsonExpression.Root; 25 | 26 | /// 27 | /// Indicate this SELECT fields contains a single result (single expression document) 28 | /// 29 | public bool IsSingleExpression => Fields.Count == 0; 30 | 31 | /// 32 | /// Indicate this SELECT fields contains at least 1 aggregate expression 33 | /// 34 | public bool HasAggregate => this.Fields.Any(x => x.IsAggregate); 35 | 36 | public SelectFields(BsonExpression docExpr) 37 | { 38 | this.SingleExpression = docExpr; 39 | this.Fields = Array.Empty(); 40 | } 41 | 42 | public SelectFields(IReadOnlyList fields) 43 | { 44 | this.SingleExpression = BsonExpression.Empty; 45 | this.Fields = fields; 46 | } 47 | 48 | public override string ToString() 49 | { 50 | return IsSingleExpression ? 51 | SingleExpression.ToString()! : 52 | string.Join(", ", Fields.Select(x => x.Name)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Sort/SortItem.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal readonly struct SortItem : IIsEmpty 4 | { 5 | public readonly RowID DataBlockID; 6 | public readonly BsonValue Key; 7 | 8 | public static readonly SortItem Empty = new(); 9 | 10 | public bool IsEmpty => this.DataBlockID.IsEmpty; 11 | 12 | public SortItem() 13 | { 14 | this.DataBlockID = RowID.Empty; 15 | this.Key = BsonValue.MinValue; 16 | } 17 | 18 | public SortItem(RowID dataBlockID, BsonValue key) 19 | { 20 | this.DataBlockID = dataBlockID; 21 | this.Key = key; 22 | } 23 | 24 | public unsafe int GetBytesCount() 25 | { 26 | return sizeof(RowID) + 27 | 1 + // data type 28 | (this.Key.IsString || this.Key.IsBinary ? sizeof(int) : 0) + // 4 for variant length 29 | this.Key.GetBytesCountCached(); 30 | } 31 | 32 | public override string ToString() => Dump.Object(this); 33 | } 34 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Structures/Sort/SortItemDocument.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal readonly struct SortItemDocument 4 | { 5 | public readonly RowID DataBlockID; 6 | public readonly BsonValue Key; 7 | public readonly BsonDocument Document; 8 | 9 | public SortItemDocument(RowID dataBlockID, BsonValue key, BsonDocument document) 10 | { 11 | this.DataBlockID = dataBlockID; 12 | this.Key = key; 13 | this.Document = document; 14 | } 15 | 16 | public override string ToString() => Dump.Object(this); 17 | } 18 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Transaction/Transaction.Abort.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// 5 | internal partial class Transaction : ITransaction 6 | { 7 | public unsafe void Abort() 8 | { 9 | using var _pc = PERF_COUNTER(150, nameof(Abort), nameof(Transaction)); 10 | 11 | // add pages to cache or decrement sharecount 12 | foreach (var ptr in _localPages.Values) 13 | { 14 | var page = (PageMemory*)ptr; 15 | 16 | if (page->IsDirty) 17 | { 18 | _memoryFactory.DeallocatePage(page); 19 | } 20 | else 21 | { 22 | // test if page is came from the cache 23 | if (page->IsPageInCache) 24 | { 25 | // return page to cache 26 | _memoryCache.ReturnPageToCache(page); 27 | } 28 | else 29 | { 30 | // try add this page in cache 31 | var added = _memoryCache.AddPageInCache(page); 32 | 33 | if (!added) 34 | { 35 | _memoryFactory.DeallocatePage(page); 36 | } 37 | } 38 | } 39 | } 40 | 41 | // clear page buffer references 42 | _localPages.Clear(); 43 | _walDirtyPages.Clear(); 44 | 45 | // restore initial values in allocation map to return original state before any change 46 | if (_initialExtendValues.Count > 0) 47 | { 48 | _allocationMapService.RestoreExtendValues(_initialExtendValues); 49 | } 50 | 51 | _initialExtendValues.Clear(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Enumerator/IndexNodeEnumerator.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal class IndexNodeEnumerator : IEnumerator 4 | { 5 | private readonly IIndexService _indexService; 6 | private readonly IndexDocument _indexDocument; 7 | 8 | private IndexNodeResult _current; 9 | private bool _init = false; 10 | private bool _eof = false; 11 | private RowID _nextID = RowID.Empty; 12 | 13 | public IndexNodeEnumerator(IIndexService indexService, IndexDocument indexDocument) 14 | { 15 | _indexService = indexService; 16 | _indexDocument = indexDocument; 17 | 18 | _current = IndexNodeResult.Empty; 19 | } 20 | 21 | public IndexNodeResult Current => _current; 22 | object IEnumerator.Current => _current; 23 | 24 | public bool MoveNext() 25 | { 26 | if (_eof) return false; 27 | 28 | if (_init == false) 29 | { 30 | _init = true; 31 | 32 | var head = _indexService.GetNode(_indexDocument.HeadIndexNodeID); 33 | 34 | if (head[0]->NextID == _indexDocument.TailIndexNodeID) return false; 35 | 36 | _current = _indexService.GetNode(head[0]->NextID); 37 | 38 | // buffer next in level 0 39 | _nextID = _current[0]->NextID; 40 | 41 | return true; 42 | } 43 | 44 | if (_nextID == _indexDocument.TailIndexNodeID) 45 | { 46 | _eof = true; 47 | return false; 48 | } 49 | 50 | _current = _indexService.GetNode(_nextID); 51 | 52 | // buffer next in level 0 53 | _nextID = _current[0]->NextID; 54 | 55 | return true; 56 | } 57 | 58 | public void Reset() 59 | { 60 | _current = IndexNodeResult.Empty; 61 | _init = false; 62 | _eof = false; 63 | } 64 | 65 | public void Dispose() 66 | { 67 | this.Reset(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/IndexKey/IndexKey.GetSize.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal partial struct IndexKey 4 | { 5 | /// 6 | /// Get how many bytes IndexKey structure will need to represent this BsonValue (should be padded) 7 | /// 8 | public static int GetSize(BsonValue value, out int valueSize) 9 | { 10 | var maxKeyLength = MAX_INDEX_KEY_SIZE - sizeof(IndexKey); 11 | 12 | valueSize = value.Type switch 13 | { 14 | BsonType.MaxValue => 0, 15 | BsonType.MinValue => 0, 16 | BsonType.Null => 0, 17 | BsonType.Boolean => 0, // use indexKey header hi 4-bytes 18 | BsonType.Int32 => 0, // use indexKey header hi 4-bytes 19 | BsonType.Int64 => sizeof(long), // 8 20 | BsonType.Double => sizeof(double), // 8 21 | BsonType.DateTime => sizeof(DateTime), // 8 22 | BsonType.ObjectId => sizeof(ObjectId), // 12 23 | BsonType.Decimal => sizeof(decimal), // 16 24 | BsonType.Guid => sizeof(Guid), // 16 25 | BsonType.String => Encoding.UTF8.GetByteCount(value.AsString), 26 | BsonType.Binary => value.AsBinary.Length, 27 | _ => throw ERR($"This object type `{value.Type}` are not supported as an index key") 28 | }; 29 | 30 | if (valueSize > maxKeyLength) throw ERR($"index value too excedded {maxKeyLength}"); 31 | 32 | var header = sizeof(IndexKey); 33 | var padding = valueSize % 8 > 0 ? 8 - (valueSize % 8) : 0; 34 | var result = header + valueSize + padding; 35 | 36 | return result; 37 | //Console.WriteLine(Dump.Object(new { Type = value.Type, header, valueSize, padding, result })); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/IndexKey/IndexKey.ToBsonValue.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal partial struct IndexKey 4 | { 5 | public static BsonValue ToBsonValue(IndexKey* indexKey) 6 | { 7 | var ptr = (nint)indexKey + sizeof(long); 8 | 9 | return indexKey->Type switch 10 | { 11 | BsonType.MinValue => BsonValue.MinValue, 12 | BsonType.MaxValue => BsonValue.MaxValue, 13 | BsonType.Null => BsonValue.Null, 14 | 15 | BsonType.Boolean => indexKey->ValueBool, 16 | BsonType.Int32 => indexKey->ValueInt32, 17 | 18 | BsonType.Int64 => *(long*)ptr, 19 | BsonType.Double => *(double*)ptr, 20 | BsonType.DateTime => *(DateTime*)ptr, 21 | 22 | BsonType.ObjectId => *(ObjectId*)ptr, 23 | BsonType.Guid => *(Guid*)ptr, 24 | BsonType.Decimal => *(decimal*)ptr, 25 | 26 | BsonType.String => Encoding.UTF8.GetString((byte*)ptr, indexKey->KeyLength), 27 | BsonType.Binary => new Span((byte*)ptr, indexKey->KeyLength).ToArray(), 28 | 29 | _ => throw new NotSupportedException() 30 | }; 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/IndexKey/IndexKey.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | [StructLayout(LayoutKind.Explicit, Size = 8, CharSet = CharSet.Ansi)] 4 | unsafe internal partial struct IndexKey 5 | { 6 | [FieldOffset(0)] public BsonType Type; // 1 7 | [FieldOffset(1)] public byte KeyLength; // 1 // do not include padding 8 | [FieldOffset(2)] public ushort Reserved; // 2 9 | 10 | [FieldOffset(4)] public bool ValueBool; // 1 11 | [FieldOffset(4)] public int ValueInt32; // 4 12 | 13 | public bool IsNull => this.Type == BsonType.Null; 14 | public bool IsMinValue => this.Type == BsonType.MinValue; 15 | public bool IsMaxValue => this.Type == BsonType.MaxValue; 16 | 17 | /// 18 | /// Get how many bytes, in memory, this IndexKey are using 19 | /// 20 | public int IndexKeySize 21 | { 22 | get 23 | { 24 | var header = sizeof(IndexKey); 25 | var valueSize = this.Type switch 26 | { 27 | BsonType.Boolean => 0, // 0 (1 but use header hi-space) 28 | BsonType.Int32 => 0, // 0 (4 but use header hi-space) 29 | _ => this.KeyLength 30 | }; 31 | var padding = valueSize % 8 > 0 ? 8 - (valueSize % 8) : 0; 32 | 33 | return header + valueSize + padding; 34 | } 35 | } 36 | 37 | public override string ToString() 38 | { 39 | fixed(IndexKey* indexKey = &this) 40 | { 41 | var value = ToBsonValue(indexKey); 42 | 43 | return Dump.Object(new { Type, KeyLength, IndexKeySize, Value = value.ToString() }); 44 | } 45 | } 46 | 47 | public static void CopyIndexKey(IndexKey* sourceIndexKey, IndexKey* targetIndexKey) 48 | { 49 | // get span from each page pointer 50 | var sourceSpan = new Span(sourceIndexKey, PAGE_SIZE); 51 | var targetSpan = new Span(targetIndexKey, PAGE_SIZE); 52 | 53 | sourceSpan.CopyTo(targetSpan); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/PageMemory/PageMemory.IndexNode.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal partial struct PageMemory // PageMemory.IndexNode 4 | { 5 | public static void InitializeAsIndexPage(PageMemory* page, uint pageID, byte colID) 6 | { 7 | page->PageID = pageID; 8 | page->PageType = PageType.Index; 9 | page->ColID = colID; 10 | 11 | page->IsDirty = true; 12 | } 13 | 14 | public static IndexNodeResult InsertIndexNode(PageMemory* page, byte slot, byte levels, BsonValue key, RowID dataBlockID, out bool defrag, out ExtendPageValue newPageValue) 15 | { 16 | // get a new index block 17 | var newIndex = PageMemory.GetFreeIndex(page); 18 | 19 | // get new rowid 20 | var indexNodeID = new RowID(page->PageID, newIndex); 21 | 22 | var nodeSize = IndexNode.GetSize(levels, key); 23 | 24 | // get page segment for this indexNode 25 | var segment = PageMemory.InsertSegment(page, (ushort)nodeSize, newIndex, true, out defrag, out newPageValue); 26 | 27 | var indexNode = (IndexNode*)((nint)page + segment->Location); 28 | 29 | // initialize indexNode 30 | indexNode->Slot = slot; 31 | indexNode->Levels = levels; 32 | indexNode->Reserved1 = 0; 33 | indexNode->Reserved2 = 0; 34 | indexNode->DataBlockID = dataBlockID; 35 | indexNode->NextNodeID = RowID.Empty; 36 | 37 | var levelPtr = (IndexNodeLevel*)((nint)indexNode + sizeof(IndexNode)); 38 | 39 | for (var l = 0; l < levels; l++) 40 | { 41 | levelPtr->NextID = levelPtr->PrevID = RowID.Empty; 42 | levelPtr++; 43 | } 44 | 45 | // after write all level nodes, levelPtr are at IndexKey location 46 | var indexKey = (IndexKey*)levelPtr; 47 | 48 | // get new indexKey and copy to memory 49 | IndexKey.Initialize(indexKey, key); 50 | 51 | return new IndexNodeResult(page, indexNodeID); 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Results/DataBlockResult.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal struct DataBlockResult : IIsEmpty 4 | { 5 | public RowID DataBlockID; 6 | 7 | public PageMemory* Page; 8 | public PageSegment* Segment; 9 | public DataBlock* DataBlock; 10 | 11 | public static DataBlockResult Empty = new() { DataBlockID = RowID.Empty, Page = default }; 12 | 13 | public bool IsEmpty => this.DataBlockID.IsEmpty; 14 | 15 | public int ContentLength => this.Segment->Length - sizeof(DataBlock) - this.DataBlock->Padding; 16 | public int DocumentLength => this.DataBlock->Extend ? -1 : 17 | *(int*)((nint)this.Page + this.Segment->Location + sizeof(DataBlock)); // read first 4 bytes on datablock as int32 in first page only 18 | 19 | public DataBlockResult(PageMemory* page, RowID dataBlockID) 20 | { 21 | ENSURE(page->PageID == dataBlockID.PageID); 22 | 23 | this.Page = page; 24 | this.DataBlockID = dataBlockID; 25 | 26 | this.Reload(); 27 | } 28 | 29 | public void Reload() 30 | { 31 | this.Segment = PageMemory.GetSegmentPtr(this.Page, this.DataBlockID.Index); 32 | this.DataBlock = (DataBlock*)((nint)this.Page + this.Segment->Location); 33 | } 34 | 35 | /// 36 | /// Get DataContent as Span 37 | /// 38 | public Span AsSpan() 39 | { 40 | return new Span((byte*)((nint)this.Page + this.Segment->Location + sizeof(DataBlock)), this.ContentLength); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Results/IndexNodeResult.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal struct IndexNodeResult : IIsEmpty 4 | { 5 | public RowID IndexNodeID; 6 | 7 | public PageMemory* Page; 8 | public PageSegment* Segment; 9 | public IndexNode* Node; 10 | public IndexKey* Key; 11 | 12 | /// 13 | /// Shortcut for get Node->DataBlockID (safe) 14 | /// 15 | public RowID DataBlockID => this.Node->DataBlockID; 16 | 17 | /// 18 | /// Shortcut for get/set Node->NextNodeID (safe) MUST set page as dirty in set!! 19 | /// 20 | public RowID NextNodeID { get => this.Node->NextNodeID; set => this.Node->NextNodeID = value; } 21 | 22 | public static IndexNodeResult Empty = new() { IndexNodeID = RowID.Empty, Page = default }; 23 | 24 | public bool IsEmpty => this.IndexNodeID.IsEmpty; 25 | 26 | public IndexNodeResult(PageMemory* page, RowID indexNodeID) 27 | { 28 | ENSURE(page->PageID == indexNodeID.PageID); 29 | 30 | this.Page = page; 31 | this.IndexNodeID = indexNodeID; 32 | 33 | this.Reload(); 34 | 35 | ENSURE(this.Segment->AsSpan(page).IsFullZero() == false); 36 | ENSURE(this.Node->Levels > 0 && this.Node->Levels <= INDEX_MAX_LEVELS); 37 | } 38 | 39 | public void Reload() 40 | { 41 | // load all pointer based on indexNodeID and &page 42 | this.Segment = PageMemory.GetSegmentPtr(this.Page, this.IndexNodeID.Index); 43 | this.Node = (IndexNode*)((nint)this.Page + this.Segment->Location); 44 | var keyOffset = this.Segment->Location + sizeof(IndexNode) + (this.Node->Levels * sizeof(IndexNodeLevel)); 45 | this.Key = (IndexKey*)((nint)this.Page + keyOffset); 46 | } 47 | 48 | public IndexNodeLevel* this[int level] 49 | { 50 | get 51 | { 52 | ENSURE(level <= this.Node->Levels); 53 | 54 | var ptr = ((nint)this.Node + sizeof(IndexNode) + (level * sizeof(IndexNodeLevel))); 55 | 56 | return (IndexNodeLevel*)ptr; 57 | } 58 | } 59 | 60 | public override string ToString() 61 | { 62 | return Dump.Object( new { IndexNodeID, DataBlockID = Node->DataBlockID, KeyType = Key->Type, KeyValue = IndexKey.ToBsonValue(Key) }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Sort/SortContainer2.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | [StructLayout(LayoutKind.Sequential, Size = (CONTAINER_SORT_SIZE_IN_PAGES * PAGE_SIZE) , Pack = 1)] 4 | unsafe internal struct SortContainer2 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Sort/SortItem2.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct SortItem2 // 16 + (n * 8) 4 | { 5 | public RowID DataBlockID; // 8 6 | public IndexKey Key; // 8 + (n * 8) 7 | } 8 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Sort/SortPage2.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | [StructLayout(LayoutKind.Sequential, Size = PAGE_SIZE, Pack = 1)] 4 | internal struct SortPage2 5 | { 6 | public int ItemsCount; // 4 7 | public int Reserved; // 4 8 | } 9 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Structs/DataBlock.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] 4 | unsafe internal struct DataBlock // 12 5 | { 6 | public byte DataFormat; // 1 7 | public bool Extend; // 1 8 | public byte Padding; // 1 (0-7) 9 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 10 | public byte Reserved; // 1 11 | 12 | public RowID NextBlockID; // 8 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Structs/IndexNode.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | unsafe internal struct IndexNode // 24 4 | { 5 | public byte Slot; // 1 6 | public byte Levels; // 1 7 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 8 | public ushort Reserved1; // 2 9 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 10 | public uint Reserved2; // 4 11 | 12 | public RowID DataBlockID; // 8 13 | public RowID NextNodeID; // 8 14 | 15 | 16 | #region Static Helpers 17 | 18 | /// 19 | /// Calculate how many bytes this node will need on page block 20 | /// 21 | public unsafe static int GetSize(int levels, BsonValue key) 22 | { 23 | return 24 | sizeof(IndexNode) + 25 | (levels * sizeof(IndexNodeLevel)) + // prev/next 26 | IndexKey.GetSize(key, out _); 27 | } 28 | 29 | #endregion 30 | } 31 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Structs/IndexNodeLevel.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal struct IndexNodeLevel // 16 4 | { 5 | public RowID PrevID; // 8 6 | public RowID NextID; // 8 7 | 8 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 9 | public RowID GetNext(int order) 10 | { 11 | return order > 0 ? this.NextID : this.PrevID; 12 | } 13 | 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public RowID GetPrev(int order) 16 | { 17 | return order > 0 ? this.PrevID : this.NextID; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/LiteDB/Engine/Unsafe/Structs/PageSegment.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | /// 4 | /// Represent a single page segment with Location (position) and Length 5 | /// 6 | unsafe internal struct PageSegment : IIsEmpty 7 | { 8 | public ushort Location; // 2 9 | public ushort Length; // 2 10 | 11 | /// 12 | /// Get final location (Location + Length) 13 | /// 14 | public ushort EndLocation => (ushort)(this.Location + this.Length); 15 | 16 | /// 17 | /// Indicate this segment are clear (no reference) 18 | /// 19 | public bool IsEmpty => this.Location == 0 && this.Length == 0; 20 | 21 | public PageSegment() 22 | { 23 | } 24 | 25 | public PageSegment(ushort location, ushort length) 26 | { 27 | this.Location = location; 28 | this.Length = length; 29 | } 30 | 31 | public void Reset() 32 | { 33 | this.Location = 0; 34 | this.Length = 0; 35 | } 36 | 37 | public Span AsSpan(PageMemory* page) 38 | { 39 | ENSURE(!this.IsEmpty); 40 | 41 | var ptr = (byte*)((nint)page + this.Location); 42 | 43 | return new Span(ptr, this.Length); 44 | } 45 | 46 | public override string ToString() => Dump.Object(this); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/Attributes/VolatileAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// When a method are decorated with this attribute means that this method can have different results for same input 5 | /// 6 | internal class VolatileAttribute: Attribute 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/BsonExpressionContext.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class BsonExpressionContext 4 | { 5 | public BsonValue Root { get; } 6 | public BsonValue Current { get; set; } 7 | public BsonDocument Parameters { get; } 8 | public Collation Collation { get; } 9 | 10 | public BsonExpressionContext(BsonValue root, BsonDocument parameters, Collation collation) 11 | { 12 | this.Root = root; 13 | this.Current = this.Root; 14 | this.Parameters = parameters; 15 | this.Collation = collation; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/BsonExpressionType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Represent all types of BsonExpressions 5 | /// 6 | public enum BsonExpressionType : byte 7 | { 8 | Constant = 1, 9 | 10 | ArrayIndex = 2, 11 | 12 | Array = 4, 13 | Document = 5, 14 | 15 | Parameter = 6, 16 | Call = 7, 17 | Root = 8, 18 | Current = 9, 19 | Path = 10, 20 | 21 | Modulo = 11, 22 | Add = 12, 23 | Subtract = 13, 24 | Multiply = 14, 25 | Divide = 15, 26 | 27 | Equal = 16, 28 | Like = 17, 29 | Between = 18, 30 | GreaterThan = 19, 31 | GreaterThanOrEqual = 20, 32 | LessThan = 21, 33 | LessThanOrEqual = 22, 34 | NotEqual = 23, 35 | In = 24, 36 | Contains = 25, 37 | 38 | Or = 30, 39 | And = 31, 40 | 41 | Inner = 32, 42 | 43 | Conditional = 40, 44 | Map = 41, 45 | Filter = 42, 46 | Sort = 43, 47 | 48 | Empty = 255 49 | } 50 | 51 | internal static class BsonExpressionExtensions 52 | { 53 | /// 54 | /// Returns if BsonExpressionType is a predicate (return a boolean). AND/OR are not in this list 55 | /// 56 | internal static bool IsPredicate(this BsonExpressionType type) 57 | { 58 | return type == BsonExpressionType.Equal || 59 | type == BsonExpressionType.Like || 60 | type == BsonExpressionType.Between || 61 | type == BsonExpressionType.GreaterThan || 62 | type == BsonExpressionType.GreaterThanOrEqual || 63 | type == BsonExpressionType.LessThan || 64 | type == BsonExpressionType.LessThanOrEqual || 65 | type == BsonExpressionType.NotEqual || 66 | type == BsonExpressionType.In || 67 | type == BsonExpressionType.Contains; 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/ExpressionMethods/Math.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal partial class BsonExpressionMethods 4 | { 5 | /// 6 | /// Apply absolute value (ABS) method in all number values 7 | /// 8 | public static BsonValue ABS(BsonValue value) 9 | { 10 | return value.Type switch 11 | { 12 | BsonType.Int32 => Math.Abs(value.AsInt32), 13 | BsonType.Int64 => Math.Abs(value.AsInt64), 14 | BsonType.Double => Math.Abs(value.AsDouble), 15 | BsonType.Decimal => Math.Abs(value.AsDecimal), 16 | _ => BsonValue.Null, 17 | }; 18 | } 19 | 20 | /// 21 | /// Round number method in all number values 22 | /// 23 | public static BsonValue ROUND(BsonValue value, BsonValue digits) 24 | { 25 | if (digits.IsNumber) 26 | { 27 | switch (value.Type) 28 | { 29 | case BsonType.Int32: return value.AsInt32; 30 | case BsonType.Int64: return value.AsInt64; 31 | case BsonType.Double: return Math.Round(value.AsDouble, digits.AsInt32); 32 | case BsonType.Decimal: return Math.Round(value.AsDecimal, digits.AsInt32); 33 | } 34 | 35 | } 36 | 37 | return BsonValue.Null; 38 | } 39 | 40 | /// 41 | /// Implement POWER (x and y) 42 | /// 43 | public static BsonValue POW(BsonValue x, BsonValue y) 44 | { 45 | if (x.IsNumber && y.IsNumber) 46 | { 47 | return Math.Pow(x.AsDouble, y.AsDouble); 48 | } 49 | 50 | return BsonValue.Null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/ArrayIndexBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class ArrayIndexBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.ArrayIndex; 6 | 7 | internal override IEnumerable Children => new[] { this.Array, this.Index }; 8 | 9 | public BsonExpression Array { get; } 10 | 11 | public BsonExpression Index { get; } 12 | 13 | public ArrayIndexBsonExpression(BsonExpression array, BsonExpression index) 14 | { 15 | this.Array = array; 16 | this.Index = index; 17 | } 18 | 19 | internal override BsonValue Execute(BsonExpressionContext context) 20 | { 21 | var arr = this.Array.Execute(context); 22 | var idx = this.Index.Execute(context); 23 | 24 | if (!arr.IsArray || !idx.IsNumber) return BsonValue.Null; 25 | 26 | var index = idx.AsInt32; 27 | var array = arr.AsArray; 28 | 29 | // adding support for negative values (backward) 30 | var i = index < 0 ? array.Count + index : index; 31 | 32 | if (i >= array.Count) return BsonValue.Null; 33 | 34 | return array[i]; 35 | } 36 | 37 | public override bool Equals(BsonExpression expr) => 38 | expr is ArrayIndexBsonExpression other && 39 | other.Array.Equals(this.Array) && 40 | other.Index.Equals(this.Index); 41 | 42 | public override int GetHashCode() => HashCode.Combine(this.Array, this.Index); 43 | 44 | public override string ToString() 45 | { 46 | return this.Array.ToString() + "[" + this.Index.ToString() + "]"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/CallBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class CallBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Call; 6 | 7 | internal override IEnumerable Children => this.Parameters; 8 | 9 | public BsonExpression[] Parameters { get; } 10 | 11 | public MethodInfo Method { get; } 12 | 13 | public bool IsVolatile { get; } 14 | 15 | private readonly bool _collation; 16 | 17 | public CallBsonExpression(MethodInfo method, BsonExpression[] parameters) 18 | { 19 | this.Method = method; 20 | this.Parameters = parameters; 21 | this.IsVolatile = method.GetCustomAttribute() is not null; 22 | 23 | _collation = method.GetParameters().FirstOrDefault()?.ParameterType == typeof(Collation); 24 | } 25 | 26 | internal override BsonValue Execute(BsonExpressionContext context) 27 | { 28 | // if contains collation parameter, must be fist parameter 29 | var values = _collation ? 30 | new object[] { context.Collation }.Union(this.Parameters.Select(x => x.Execute(context))) : 31 | this.Parameters.Select(x => x.Execute(context)); 32 | 33 | return (BsonValue)this.Method.Invoke(null, values.ToArray()); 34 | } 35 | 36 | public override bool Equals(BsonExpression expr) => 37 | expr is CallBsonExpression other && 38 | other.Method.Name == this.Method.Name && 39 | other.Parameters.SequenceEqual(this.Parameters) && 40 | other.IsVolatile == this.IsVolatile; 41 | 42 | public override int GetHashCode() => HashCode.Combine(this.Method, this.Parameters); 43 | 44 | public override string ToString() 45 | { 46 | return this.Method.Name + "(" + string.Join(",", this.Parameters.Select(x => x.ToString())) + ")"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/ConditionalBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class ConditionalBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Conditional; 6 | 7 | internal override IEnumerable Children => new BsonExpression[] { IfTest, TrueExpr, FalseExpr }; 8 | 9 | public BsonExpression IfTest { get; } 10 | public BsonExpression TrueExpr { get; } 11 | public BsonExpression FalseExpr { get; } 12 | 13 | public ConditionalBsonExpression(BsonExpression ifTest, BsonExpression trueExpr, BsonExpression falseExpr) 14 | { 15 | this.IfTest = ifTest; 16 | this.TrueExpr = trueExpr; 17 | this.FalseExpr = falseExpr; 18 | } 19 | 20 | internal override BsonValue Execute(BsonExpressionContext context) 21 | { 22 | var result = this.IfTest.Execute(context); 23 | 24 | if (result.IsBoolean && result.AsBoolean) 25 | { 26 | return this.TrueExpr.Execute(context); 27 | } 28 | else 29 | { 30 | return this.FalseExpr.Execute(context); 31 | } 32 | } 33 | 34 | public override bool Equals(BsonExpression expr) => 35 | expr is ConditionalBsonExpression other && 36 | other.IfTest == this.IfTest && 37 | other.TrueExpr == this.TrueExpr && 38 | other.FalseExpr == this.FalseExpr; 39 | 40 | public override int GetHashCode() => HashCode.Combine(IfTest, TrueExpr, FalseExpr); 41 | 42 | public override string ToString() 43 | { 44 | return this.IfTest.ToString() + "?" + this.TrueExpr.ToString() + ":" + this.FalseExpr.ToString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/ConstantBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class ConstantBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Constant; 6 | 7 | public BsonValue Value { get; } 8 | 9 | public ConstantBsonExpression(BsonValue value) 10 | { 11 | this.Value = value; 12 | } 13 | 14 | internal override BsonValue Execute(BsonExpressionContext context) 15 | { 16 | return this.Value; 17 | } 18 | 19 | public override bool Equals(BsonExpression expr) => 20 | expr is ConstantBsonExpression other && 21 | other.Value.Equals(this.Value); 22 | 23 | public override int GetHashCode() => this.Value.GetHashCode(); 24 | 25 | public override string ToString() 26 | { 27 | return this.Value.ToString(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/EmptyBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class EmptyBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Empty; 6 | 7 | public EmptyBsonExpression() 8 | { 9 | } 10 | 11 | internal override BsonValue Execute(BsonExpressionContext context) 12 | { 13 | throw new NotSupportedException(); 14 | } 15 | 16 | public override bool Equals(BsonExpression expr) => expr is EmptyBsonExpression; 17 | 18 | public override int GetHashCode() => this.Type.GetHashCode(); 19 | 20 | public override string ToString() 21 | { 22 | return ""; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/FilterBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class FilterBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Map; 6 | 7 | internal override IEnumerable Children => new[] { this.Source, this.Selector }; 8 | 9 | public BsonExpression Source { get; } 10 | 11 | public BsonExpression Selector { get; } 12 | 13 | public FilterBsonExpression(BsonExpression source, BsonExpression selector) 14 | { 15 | this.Source = source; 16 | this.Selector = selector; 17 | } 18 | 19 | internal override BsonValue Execute(BsonExpressionContext context) 20 | { 21 | IEnumerable source() 22 | { 23 | var src = this.Source.Execute(context); 24 | 25 | if (!src.IsArray) yield break; 26 | 27 | foreach(var item in src.AsArray) 28 | { 29 | context.Current = item; 30 | 31 | var value = this.Selector.Execute(context); 32 | 33 | if (value.IsBoolean && value.AsBoolean) 34 | { 35 | yield return item; 36 | } 37 | 38 | context.Current = context.Root; 39 | } 40 | }; 41 | 42 | return new BsonArray(source()); 43 | } 44 | 45 | public override bool Equals(BsonExpression expr) => 46 | expr is FilterBsonExpression other && 47 | other.Source.Equals(this.Source) && 48 | other.Selector.Equals(this.Selector); 49 | 50 | public override int GetHashCode() => HashCode.Combine(this.Source, this.Selector); 51 | 52 | public override string ToString() 53 | { 54 | return this.Source.ToString() + "[" + this.Selector.ToString() + "]"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/InnerBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class InnerBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Inner; 6 | 7 | internal override IEnumerable Children => new[] { this.InnerExpression }; 8 | 9 | public BsonExpression InnerExpression { get; } 10 | 11 | public InnerBsonExpression(BsonExpression inner) 12 | { 13 | this.InnerExpression = inner; 14 | } 15 | 16 | internal override BsonValue Execute(BsonExpressionContext context) 17 | { 18 | return this.InnerExpression.Execute(context); 19 | } 20 | 21 | public override bool Equals(BsonExpression expr) => 22 | expr is InnerBsonExpression other && 23 | other.InnerExpression.Equals(this.InnerExpression); 24 | 25 | public override int GetHashCode() => this.InnerExpression.GetHashCode(); 26 | 27 | public override string ToString() 28 | { 29 | return "(" + this.InnerExpression.ToString() + ")"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/MakeArrayBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class MakeArrayBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Array; 6 | 7 | internal override IEnumerable Children => this.Items; 8 | 9 | public IEnumerable Items { get; } 10 | 11 | public MakeArrayBsonExpression(IEnumerable items) 12 | { 13 | this.Items = items; 14 | } 15 | 16 | internal override BsonValue Execute(BsonExpressionContext context) 17 | { 18 | return new BsonArray(this.Items.Select(x => x.Execute(context))); 19 | } 20 | 21 | public override bool Equals(BsonExpression expr) => 22 | expr is MakeArrayBsonExpression other && 23 | other.Items.SequenceEqual(this.Items); 24 | 25 | public override int GetHashCode() => this.Items.GetHashCode(); 26 | 27 | public override string ToString() 28 | { 29 | return "[" + String.Join(",", this.Items.Select(x => x.ToString())) + "]"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/MakeDocumentBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class MakeDocumentBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Document; 6 | 7 | internal override IEnumerable Children => this.Values.Values; 8 | 9 | public IDictionary Values { get; } 10 | 11 | public MakeDocumentBsonExpression(IDictionary values) 12 | { 13 | this.Values = values; 14 | } 15 | 16 | internal override BsonValue Execute(BsonExpressionContext context) 17 | { 18 | return new BsonDocument(this.Values.ToDictionary(x => x.Key, x => x.Value.Execute(context))); 19 | } 20 | 21 | public override bool Equals(BsonExpression expr) => 22 | expr is MakeDocumentBsonExpression other && 23 | other.Values.Keys.SequenceEqual(this.Values.Keys) && 24 | other.Values.Values.SequenceEqual(this.Values.Values); 25 | 26 | public override int GetHashCode() => this.Values.GetHashCode(); 27 | 28 | public override string ToString() 29 | { 30 | return "{" + String.Join(",", this.Values.Select(x => 31 | (x.Key.IsWord() ? x.Key : $"\"{x.Key}\"") + ":" + 32 | x.Value.ToString())) + "}"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/MapBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class MapBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Map; 6 | 7 | internal override IEnumerable Children => new[] { this.Source, this.Selector }; 8 | 9 | public BsonExpression Source { get; } 10 | 11 | public BsonExpression Selector { get; } 12 | 13 | public MapBsonExpression(BsonExpression source, BsonExpression selector) 14 | { 15 | this.Source = source; 16 | this.Selector = selector; 17 | } 18 | 19 | internal override BsonValue Execute(BsonExpressionContext context) 20 | { 21 | return new BsonArray(getSource(this.Source, this.Selector, context)); 22 | 23 | static IEnumerable getSource(BsonExpression source, BsonExpression selector, BsonExpressionContext context) 24 | { 25 | var src = source.Execute(context); 26 | 27 | if (src is BsonArray array) 28 | { 29 | foreach (var item in array) 30 | { 31 | context.Current = item; 32 | 33 | var value = selector.Execute(context); 34 | 35 | yield return value; 36 | 37 | context.Current = context.Root; 38 | } 39 | } 40 | }; 41 | } 42 | 43 | public override bool Equals(BsonExpression expr) => 44 | expr is MapBsonExpression other && 45 | other.Source.Equals(this.Source) && 46 | other.Selector.Equals(this.Selector); 47 | 48 | public override int GetHashCode() => HashCode.Combine(this.Source, this.Selector); 49 | 50 | public override string ToString() 51 | { 52 | return this.Source.ToString() + "=>" + this.Selector.ToString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/ParameterBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class ParameterBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Parameter; 6 | 7 | public string Name { get; } 8 | 9 | public ParameterBsonExpression(string name) 10 | { 11 | this.Name = name; 12 | } 13 | 14 | internal override BsonValue Execute(BsonExpressionContext context) 15 | { 16 | return context.Parameters[this.Name]; 17 | } 18 | 19 | public override bool Equals(BsonExpression expr) => 20 | expr is ParameterBsonExpression other && 21 | other.Name == this.Name; 22 | 23 | public override int GetHashCode() => this.Name.GetHashCode(); 24 | 25 | public override string ToString() 26 | { 27 | return "@" + this.Name; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/PathBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class PathBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => BsonExpressionType.Path; 6 | 7 | internal override IEnumerable Children => new[] { this.Source }; 8 | 9 | public string Field { get; } 10 | 11 | public BsonExpression Source { get; } 12 | 13 | public PathBsonExpression(BsonExpression source, string field) 14 | { 15 | this.Field = field; 16 | this.Source = source; 17 | } 18 | 19 | internal override BsonValue Execute(BsonExpressionContext context) 20 | { 21 | var source = this.Source.Execute(context); 22 | 23 | if (this.Field == null) return source; 24 | 25 | if (source.IsDocument) 26 | { 27 | // return document field (or null if not exists) 28 | return source.AsDocument[this.Field]; 29 | } 30 | else if (source.IsArray) 31 | { 32 | // returns document fields inside array (only for sub documents) 33 | // ex: $.Name - where $ = [{Name:.., Age:..},{Name:..}] => [Name0, Name1, ...] 34 | return new BsonArray(source.AsArray.Select(x => x.IsDocument ? x.AsDocument[this.Field] : BsonValue.Null)); 35 | } 36 | else 37 | { 38 | return BsonValue.Null; 39 | } 40 | } 41 | 42 | public override bool Equals(BsonExpression expr) => 43 | expr is PathBsonExpression other && 44 | other.Source.Equals(this.Source) && 45 | other.Field == this.Field; 46 | 47 | public override int GetHashCode() => HashCode.Combine(this.Source, this.Field); 48 | 49 | public override string ToString() 50 | { 51 | var field = this.Field.IsWord() ? 52 | this.Field : 53 | "[\"" + this.Field + "\"]"; 54 | 55 | return this.Source.ToString() + "." + field; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/InternalExpressions/ScopeBsonExpression.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal class ScopeBsonExpression : BsonExpression 4 | { 5 | public override BsonExpressionType Type => this.IsRoot ? BsonExpressionType.Root : BsonExpressionType.Current; 6 | 7 | private bool IsRoot { get; } 8 | 9 | public ScopeBsonExpression(bool root) 10 | { 11 | this.IsRoot = root; 12 | } 13 | 14 | internal override BsonValue Execute(BsonExpressionContext context) 15 | { 16 | return this.IsRoot ? context.Root : context.Current; 17 | } 18 | 19 | 20 | public override bool Equals(BsonExpression expr) => 21 | expr is ScopeBsonExpression other && 22 | other.IsRoot == this.IsRoot; 23 | 24 | public override int GetHashCode() => this.IsRoot.GetHashCode(); 25 | 26 | public override string ToString() 27 | { 28 | return this.IsRoot ? "$" : "@"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LiteDB/Expression/Tokenizer/TokenType.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// ASCII char names: https://www.ascii.cl/htmlcodes.htm 5 | /// 6 | internal enum TokenType 7 | { 8 | /// { 9 | OpenBrace, 10 | /// } 11 | CloseBrace, 12 | /// [ 13 | OpenBracket, 14 | /// ] 15 | CloseBracket, 16 | /// ( 17 | OpenParenthesis, 18 | /// ) 19 | CloseParenthesis, 20 | /// , 21 | Comma, 22 | /// : 23 | Colon, 24 | /// ; 25 | SemiColon, 26 | /// => 27 | Arrow, 28 | /// @ 29 | At, 30 | /// # 31 | Hashtag, 32 | /// ~ 33 | Til, 34 | /// . 35 | Period, 36 | /// & 37 | Ampersand, 38 | /// ? 39 | Question, 40 | /// $ 41 | Dollar, 42 | /// ! 43 | Exclamation, 44 | /// != 45 | NotEquals, 46 | /// = 47 | Equals, 48 | /// > 49 | Greater, 50 | /// >= 51 | GreaterOrEquals, 52 | /// < 53 | Less, 54 | /// <= 55 | LessOrEquals, 56 | /// - 57 | Minus, 58 | /// + 59 | Plus, 60 | /// * 61 | Asterisk, 62 | /// / 63 | Slash, 64 | /// \ 65 | Backslash, 66 | /// % 67 | Percent, 68 | /// "..." or '...' 69 | String, 70 | /// [0-9]+ 71 | Int, 72 | /// [0-9]+.[0-9] 73 | Double, 74 | /// \n\r\t \u0032 75 | Whitespace, 76 | /// [a-Z_$]+[a-Z0-9_$] 77 | Word, 78 | EOF, 79 | Unknown, 80 | 81 | /// Uninitalized token 82 | Empty 83 | } 84 | -------------------------------------------------------------------------------- /src/LiteDB/LiteDB.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6 5 | 11 6 | 6.0.0 7 | 6.0.0 8 | 6.0.0 9 | enable 10 | Maurício David 11 | LiteDB 12 | LiteDB - A lightweight embedded .NET NoSQL document store in a single datafile 13 | Apache 14 | en-US 15 | LiteDB 16 | LiteDB 17 | 6.0.0 18 | database nosql embedded 19 | icon_64x64.png 20 | LICENSE 21 | https://www.litedb.org 22 | https://github.com/mbdavid/LiteDB 23 | git 24 | LiteDB 25 | LiteDB 26 | true 27 | 1701;1702;1705;1591;0618 28 | bin\$(Configuration)\$(TargetFramework)\LiteDB.xml 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/LiteDB/Utils/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | namespace System.Runtime.CompilerServices; 2 | 3 | /// 4 | /// Class definition for works with "init;" keyword in .netstandard project 5 | /// 6 | public class IsExternalInit 7 | { 8 | } 9 | 10 | /// 11 | /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. 12 | /// 13 | [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] 14 | public sealed class CompilerFeatureRequiredAttribute : Attribute 15 | { 16 | public CompilerFeatureRequiredAttribute(string featureName) 17 | { 18 | FeatureName = featureName; 19 | } 20 | 21 | /// 22 | /// The name of the compiler feature. 23 | /// 24 | public string FeatureName { get; } 25 | 26 | /// 27 | /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . 28 | /// 29 | public bool IsOptional { get; init; } 30 | 31 | /// 32 | /// The used for the ref structs C# feature. 33 | /// 34 | public const string RefStructs = nameof(RefStructs); 35 | 36 | /// 37 | /// The used for the required members C# feature. 38 | /// 39 | public const string RequiredMembers = nameof(RequiredMembers); 40 | } 41 | 42 | /// Specifies that a type has required members or that a member is required. 43 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 44 | #if SYSTEM_PRIVATE_CORELIB 45 | public 46 | #else 47 | internal 48 | #endif 49 | sealed class RequiredMemberAttribute : Attribute 50 | { 51 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Crc8.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// Calculate CRC-8 (1 byte) 5 | /// https://blog.csdn.net/plutus_sutulp/article/details/8473377 6 | /// 7 | internal static class Crc8 8 | { 9 | static byte[] _table = new byte[256]; 10 | // x8 + x7 + x6 + x4 + x2 + 1 11 | const byte poly = 0xd5; 12 | 13 | /// 14 | /// Static initializer _table 15 | /// 16 | static Crc8() 17 | { 18 | for (var i = 0; i < 256; ++i) 19 | { 20 | var temp = i; 21 | 22 | for (var j = 0; j < 8; ++j) 23 | { 24 | if ((temp & 0x80) != 0) 25 | { 26 | temp = (temp << 1) ^ poly; 27 | } 28 | else 29 | { 30 | temp <<= 1; 31 | } 32 | } 33 | 34 | _table[i] = (byte)temp; 35 | } 36 | } 37 | 38 | public static byte ComputeChecksum(Span span) 39 | { 40 | using var _pc = PERF_COUNTER(170, nameof(ComputeChecksum), nameof(Crc8)); 41 | 42 | byte crc = 0; 43 | 44 | for (var i = 0; i < span.Length; i++) 45 | { 46 | var b = span[i]; 47 | 48 | crc = _table[crc ^ b]; 49 | } 50 | 51 | return crc; 52 | } 53 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Debugger/CleanerExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// A quick and dirty cleaner names from LINQ expression (debug use only) 5 | /// 6 | internal static class CleanerExtensions 7 | { 8 | /// 9 | /// Clean TypeName (remove async strings) 10 | /// 11 | public static string CleanName(this Type type) 12 | { 13 | var str = type.Name; 14 | 15 | str = Regex.Replace(str, @"^<(.*)>.*", "$1"); 16 | 17 | return str; 18 | } 19 | 20 | /// 21 | /// Clean a LINQ expression 22 | /// 23 | public static string Clean(this Expression e) 24 | { 25 | var str = e.ToString(); 26 | 27 | str = Regex.Replace(str, @"value\(.*?\)\.", ""); 28 | str = Regex.Replace(str, @"^value\(.*\.(.*)\)$", "$1"); 29 | str = Regex.Replace(str, @" AndAlso ", " && "); 30 | str = Regex.Replace(str, @" OrElse ", " || "); 31 | 32 | str = Regex.Replace(str, @"^\((.*)\)$", "$1"); 33 | 34 | return str; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/LiteDB/Utils/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB.Engine; 2 | 3 | internal static class ExceptionExtensions 4 | { 5 | /// 6 | /// Handle critial LiteDB exceptions to avoid 7 | /// 8 | public static void HandleError(this Exception exception, IServicesFactory factory) 9 | { 10 | // any .net exception is critical (except "ArgumentException") 11 | // or LiteException with code >= 900 12 | var isCritical = exception is LiteException ex ? 13 | ex.IsCritical : exception is not ArgumentException; 14 | 15 | //TODO: do log from factory 16 | 17 | if (isCritical) 18 | { 19 | factory.Exception = exception; 20 | factory.Dispose(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Exceptions/LiteException.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// The main exception for LiteDB 5 | /// 6 | public partial class LiteException : Exception 7 | { 8 | public int ErrorCode { get; } 9 | public long Position { get; private set; } 10 | 11 | public bool IsCritical => this.ErrorCode >= 900; 12 | 13 | private LiteException(int code, string message) 14 | : base(message) 15 | { 16 | this.ErrorCode = code; 17 | } 18 | 19 | private LiteException(int code, string message, Exception? inner) 20 | : base(message, inner) 21 | { 22 | this.ErrorCode = code; 23 | } 24 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Extensions/DateExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal static class DateExtensions 4 | { 5 | /// 6 | /// Truncate DateTime in milliseconds 7 | /// 8 | public static DateTime Truncate(this DateTime dt) 9 | { 10 | if (dt == DateTime.MaxValue || dt == DateTime.MinValue) 11 | { 12 | return dt; 13 | } 14 | 15 | return new DateTime(dt.Year, dt.Month, dt.Day, 16 | dt.Hour, dt.Minute, dt.Second, dt.Millisecond, 17 | dt.Kind); 18 | } 19 | 20 | // https://github.com/dotnet/runtime/issues/65858#issuecomment-1050836263 21 | public static TimeSpan GetElapsedTime(long start) 22 | => new TimeSpan((long)((double)(Stopwatch.GetTimestamp() - start) * TICK_FREQUENCY)); 23 | 24 | public static int MonthDifference(this DateTime startDate, DateTime endDate) 25 | { 26 | // https://stackoverflow.com/a/1526116/3286260 27 | 28 | int compMonth = (endDate.Month + endDate.Year * 12) - (startDate.Month + startDate.Year * 12); 29 | double daysInEndMonth = (endDate - endDate.AddMonths(1)).Days; 30 | double months = compMonth + (startDate.Day - endDate.Day) / daysInEndMonth; 31 | 32 | return Convert.ToInt32(Math.Truncate(months)); 33 | } 34 | 35 | public static int YearDifference(this DateTime startDate, DateTime endDate) 36 | { 37 | // https://stackoverflow.com/a/28444291/3286260 38 | 39 | //Excel documentation says "COMPLETE calendar years in between dates" 40 | int years = endDate.Year - startDate.Year; 41 | 42 | if (startDate.Month == endDate.Month &&// if the start month and the end month are the same 43 | endDate.Day < startDate.Day)// BUT the end day is less than the start day 44 | { 45 | years--; 46 | } 47 | else if (endDate.Month < startDate.Month)// if the end month is less than the start month 48 | { 49 | years--; 50 | } 51 | 52 | return years; 53 | } 54 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Extensions/HashSetExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal static class HashSetExtensions 4 | { 5 | public static HashSet AddRange(this HashSet hash, IEnumerable items) 6 | { 7 | if (items is null) return hash; 8 | 9 | foreach(var item in items) 10 | { 11 | hash.Add(item); 12 | } 13 | 14 | return hash; 15 | } 16 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Extensions/IOExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal static class IOExceptionExtensions 4 | { 5 | private const int ERROR_SHARING_VIOLATION = 32; 6 | private const int ERROR_LOCK_VIOLATION = 33; 7 | 8 | /// 9 | /// Detect if exception is an Locked exception 10 | /// 11 | public static bool IsLocked(this IOException ex) 12 | { 13 | var errorCode = Marshal.GetHRForException(ex) & ((1 << 16) - 1); 14 | 15 | return 16 | errorCode == ERROR_SHARING_VIOLATION || 17 | errorCode == ERROR_LOCK_VIOLATION; 18 | } 19 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Extensions/LinqExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal static class LinqExtensions 4 | { 5 | public static IEnumerable> Batch(this IEnumerable source, int batchSize) 6 | { 7 | using (var enumerator = source.GetEnumerator()) 8 | { 9 | while (enumerator.MoveNext()) 10 | { 11 | yield return YieldBatchElements(enumerator, batchSize - 1); 12 | } 13 | } 14 | } 15 | 16 | private static IEnumerable YieldBatchElements(IEnumerator source, int batchSize) 17 | { 18 | yield return source.Current; 19 | 20 | for (int i = 0; i < batchSize && source.MoveNext(); i++) 21 | { 22 | yield return source.Current; 23 | } 24 | } 25 | 26 | public static IEnumerable DistinctBy(this IEnumerable source, 27 | Func keySelector, IEqualityComparer comparer) 28 | { 29 | if (source == null) throw new ArgumentNullException(nameof(source)); 30 | if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); 31 | 32 | return _(); 33 | 34 | IEnumerable _() 35 | { 36 | var knownKeys = new HashSet(comparer); 37 | 38 | foreach (var element in source) 39 | { 40 | if (knownKeys.Add(keySelector(element))) 41 | { 42 | yield return element; 43 | } 44 | } 45 | } 46 | } 47 | 48 | /// 49 | /// Return same IEnumerable but indicate if item last item in enumerable 50 | /// 51 | public static IEnumerable> IsLast(this IEnumerable source) where T : class 52 | { 53 | T? last = default; 54 | 55 | foreach(var item in source) 56 | { 57 | if (last != default) 58 | { 59 | yield return new LastItem { Item = last, IsLast = false }; 60 | } 61 | 62 | last = item; 63 | } 64 | 65 | if (last != null) 66 | { 67 | yield return new LastItem { Item = last, IsLast = true }; 68 | } 69 | } 70 | } 71 | 72 | internal struct LastItem 73 | { 74 | public T Item { get; set; } 75 | public bool IsLast { get; set; } 76 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Extensions/MarshalExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal unsafe static class MarshalEx 4 | { 5 | 6 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 7 | public static void FillZero(byte* ptr, int length) 8 | { 9 | var span = new Span(ptr, length); 10 | 11 | span.Fill(0); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Interfaces/IIsEmpty.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal interface IIsEmpty 4 | { 5 | bool IsEmpty { get; } 6 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/Randomizer.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// A singleton shared randomizer class 5 | /// 6 | internal static class Randomizer 7 | { 8 | private static readonly Random _random = new(RANDOMIZER_SEED); 9 | 10 | public static int Next() 11 | { 12 | #if DEBUG 13 | lock (_random) 14 | { 15 | return _random.Next(); 16 | } 17 | #else 18 | return Random.Shared.Next(); 19 | #endif 20 | } 21 | 22 | public static int Next(int minValue, int maxValue) 23 | { 24 | #if DEBUG 25 | lock (_random) 26 | { 27 | return _random.Next(minValue, maxValue); 28 | } 29 | #else 30 | return Random.Shared.Next(minValue, maxValue); 31 | #endif 32 | } 33 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/SharedArray.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | /// 4 | /// A shared byte array to rent and return on dispose 5 | /// 6 | public readonly struct SharedArray : IDisposable 7 | { 8 | private readonly T[] _array; 9 | private readonly int _length; 10 | 11 | private SharedArray(T[] array, int length) 12 | { 13 | _array = array; 14 | _length = length; 15 | } 16 | 17 | public T this[int index] 18 | { 19 | get => _array[index]; 20 | set => _array[index] = value; 21 | } 22 | 23 | public readonly Span AsSpan() => _array.AsSpan(0, _length); 24 | 25 | public readonly Span AsSpan(int start) => _array.AsSpan(start); 26 | 27 | public readonly Span AsSpan(int start, int length) => _array.AsSpan(start, length); 28 | 29 | public static SharedArray Rent(int length) 30 | { 31 | ENSURE(length < int.MaxValue, new { length }); 32 | 33 | var array = ArrayPool.Shared.Rent(length); 34 | 35 | return new (array, length); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | ArrayPool.Shared.Return(_array); 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return Dump.Object(new { _length }); 46 | } 47 | } -------------------------------------------------------------------------------- /src/LiteDB/Utils/StringBuilderCache.cs: -------------------------------------------------------------------------------- 1 | namespace LiteDB; 2 | 3 | internal static class StringBuilderCache 4 | { 5 | private static StringBuilder? _cache; 6 | 7 | public static StringBuilder Acquire() 8 | { 9 | var sb = _cache; 10 | 11 | if (sb is not null) 12 | { 13 | _cache = null; 14 | sb.Clear(); 15 | return sb; 16 | } 17 | 18 | return new StringBuilder(); 19 | } 20 | 21 | public static string Release(StringBuilder sb) 22 | { 23 | var result = sb.ToString(); 24 | 25 | _cache = sb; 26 | 27 | return result; 28 | } 29 | } -------------------------------------------------------------------------------- /src/LiteDB/_Imports.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Globalization; 3 | global using System.Collections; 4 | global using System.Collections.Generic; 5 | global using System.Collections.Concurrent; 6 | global using System.Text; 7 | global using System.Threading; 8 | global using System.Threading.Tasks; 9 | global using System.Linq; 10 | global using System.Linq.Expressions; 11 | global using System.IO; 12 | global using System.Text.RegularExpressions; 13 | global using System.Runtime.InteropServices; 14 | global using System.Runtime.CompilerServices; 15 | global using System.Security; 16 | global using System.Diagnostics; 17 | global using System.Diagnostics.CodeAnalysis; 18 | global using System.Security.Cryptography; 19 | global using System.Reflection; 20 | global using System.Buffers; 21 | global using System.Buffers.Binary; 22 | 23 | global using LiteDB.Engine; 24 | global using static LiteDB.Constants; 25 | global using static LiteDB.LiteException; 26 | global using static LiteDB.CodeContract; 27 | global using static LiteDB.Profiler; 28 | 29 | [assembly: InternalsVisibleTo("ConsoleApp1")] 30 | [assembly: InternalsVisibleTo("LiteDB.Tests")] 31 | [assembly: InternalsVisibleTo("LiteDB.Benchmark")] 32 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 33 | 34 | -------------------------------------------------------------------------------- /src/LiteDB/icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbdavid/LiteDB-vNext/2941736fcf4e8efb23e8c8228a6d6532e361702a/src/LiteDB/icon_64x64.png --------------------------------------------------------------------------------