From 15eb9a181d1ac57157b39a069d1910504a696be9 Mon Sep 17 00:00:00 2001 From: oruchreis Date: Tue, 5 Dec 2023 19:13:14 +0300 Subject: [PATCH] v0.4.0: Dsl Query Builder, query interface for grpc and embedded, completed CJsonWriter --- CHANGELOG.md | 6 + README.md | 18 +- Tests/ReindexerNet.CoreTest/BaseTest.cs | 484 +++++---- .../SelectBenchmark.cs | 24 +- .../ReindexerNet.EmbeddedTest/EmbeddedTest.cs | 10 +- .../ReindexerBindingTest.cs | 61 +- src/ReindexerNet.Core/CJsonQueryBuilder.cs | 995 ++++++++++++++++++ .../IAsyncReindexerClient.cs | 27 + src/ReindexerNet.Core/IFilterQueryBuilder.cs | 95 ++ src/ReindexerNet.Core/IQueryBuilder.cs | 263 +++++ src/ReindexerNet.Core/IReindexerClient.cs | 26 +- src/ReindexerNet.Core/IUpdateQueryBuilder.cs | 35 + src/ReindexerNet.Core/Internal/Bindings.cs | 224 ++++ src/ReindexerNet.Core/Internal/CJsonReader.cs | 386 +++++++ src/ReindexerNet.Core/Internal/CJsonWriter.cs | 230 ++++ src/ReindexerNet.Core/Model/Condtion.cs | 63 ++ src/ReindexerNet.Core/Model/Query.cs | 13 + src/ReindexerNet.Core/NetStandard20Fixes.cs | 299 ++++++ .../ReindexerJsonSerializer.cs | 97 +- .../ReindexerNet.Core.csproj | 7 +- .../SerializableQueryBuilder.cs | 490 +++++++++ ...bedded.NativeAssets.AlpineLinux-x64.csproj | 6 +- ...Net.Embedded.NativeAssets.Linux-x64.csproj | 6 +- ...erNet.Embedded.NativeAssets.Osx-x64.csproj | 6 +- ...erNet.Embedded.NativeAssets.Win-x64.csproj | 6 +- ...erNet.Embedded.NativeAssets.Win-x86.csproj | 6 +- .../EmbeddedTransactionInvoker.cs | 158 +-- .../Internal/BindingHelpers.cs | 74 +- .../Internal/CJsonReader.cs | 379 ------- .../Internal/CJsonWriter.cs | 171 --- src/ReindexerNet.Embedded/Internal/CTypes.cs | 353 +++---- .../Internal/Helpers/TypeHelper.cs | 2 +- .../Internal/ReindexerBinding.cs | 6 +- .../ReindexerEmbedded.cs | 177 +++- .../ReindexerEmbeddedServer.cs | 8 +- .../ReindexerNet.Embedded.csproj | 6 +- .../ReindexerGrpcClient.cs | 81 +- .../ReindexerNet.Remote.Grpc.csproj | 6 +- .../ReindexerNet.Remote.OpenApi.csproj | 6 +- 39 files changed, 4116 insertions(+), 1194 deletions(-) create mode 100644 src/ReindexerNet.Core/CJsonQueryBuilder.cs create mode 100644 src/ReindexerNet.Core/IFilterQueryBuilder.cs create mode 100644 src/ReindexerNet.Core/IQueryBuilder.cs create mode 100644 src/ReindexerNet.Core/IUpdateQueryBuilder.cs create mode 100644 src/ReindexerNet.Core/Internal/Bindings.cs create mode 100644 src/ReindexerNet.Core/Internal/CJsonReader.cs create mode 100644 src/ReindexerNet.Core/Internal/CJsonWriter.cs create mode 100644 src/ReindexerNet.Core/Model/Condtion.cs create mode 100644 src/ReindexerNet.Core/NetStandard20Fixes.cs create mode 100644 src/ReindexerNet.Core/SerializableQueryBuilder.cs delete mode 100644 src/ReindexerNet.Embedded/Internal/CJsonReader.cs delete mode 100644 src/ReindexerNet.Embedded/Internal/CJsonWriter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a5db1..b68cc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2023-12-07 +### Added +- Dsl Query Builder +- Query Interface for both grpc and embedded clients. +- Completed cjson serializer(CJsonWriter) + ## [0.3.10] - 2023-11-30 ### Added - Updated Reindexer Embedded to 3.20 diff --git a/README.md b/README.md index 093fd0f..68a6751 100644 --- a/README.md +++ b/README.md @@ -30,19 +30,19 @@ If you have any questions about Reindexer, please use [main page](https://github + Condition="$([MSBuild]::IsOSPlatform('Linux')) and ($(RuntimeIdentifier.StartsWith('linux-musl')) or $(RuntimeIdentifier.StartsWith('alpine')))" /> + Condition="$([MSBuild]::IsOSPlatform('Linux')) and !($(RuntimeIdentifier.StartsWith('linux-musl')) or $(RuntimeIdentifier.StartsWith('alpine')))" /> + Condition="$([MSBuild]::IsOSPlatform('OSX'))" /> + Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> + Condition="$([MSBuild]::IsOSPlatform('Windows'))" /> ``` @@ -292,11 +292,11 @@ Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical - [x] RestApi models [![Core Nuget](https://img.shields.io/nuget/v/ReindexerNet.Core?label=Core&color=1182c2&logo=nuget)](https://www.nuget.org/packages/ReindexerNet.Core) - [x] Embedded mode binding (Builtin) [![Embedded Nuget](https://img.shields.io/nuget/v/ReindexerNet.Embedded?label=Embedded&color=1182c2&logo=nuget)](https://www.nuget.org/packages/ReindexerNet.Embedded) - [x] Embedded Server mode binding (Builtin-server) [![Embedded Nuget](https://img.shields.io/nuget/v/ReindexerNet.Embedded?label=Embedded&color=1182c2&logo=nuget)](https://www.nuget.org/packages/ReindexerNet.Embedded) - - [x] Embedded bindings for win-x64, linux-x64, osx-x64 and win-x86 + - [x] Embedded bindings for win-x64, linux-x64, linux-musl-x64, osx-x64 and win-x86 - [x] GRPC connector for remote servers (Standalone server/Grpc) [![Remote.Grpc Nuget](https://img.shields.io/nuget/v/ReindexerNet.Remote.Grpc?label=Remote.Grpc&color=1182c2&logo=nuget)](https://www.nuget.org/packages/ReindexerNet.Remote.Grpc) - - [ ] Query Interface - - [ ] CJson Serializer - - [ ] Dsl Query Builder + - [x] Query Interface + - [x] CJson Serializer + - [x] Dsl Query Builder - [ ] OpenApi/Json connector for remote servers - [ ] CProto connector for remote servers - [ ] Documentation diff --git a/Tests/ReindexerNet.CoreTest/BaseTest.cs b/Tests/ReindexerNet.CoreTest/BaseTest.cs index 712dd4e..2f335e1 100644 --- a/Tests/ReindexerNet.CoreTest/BaseTest.cs +++ b/Tests/ReindexerNet.CoreTest/BaseTest.cs @@ -1,256 +1,324 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; +using System.Text; +using System.Text.Json; using System.Threading.Tasks; -namespace ReindexerNet.CoreTest +namespace ReindexerNet.CoreTest; + +[TestClass] +public abstract class BaseTest + where TClient : IAsyncReindexerClient { - [TestClass] - public abstract class BaseTest - where TClient : IAsyncReindexerClient + private readonly bool _testModifiedItemCount; + private readonly bool _testPrecepts; + + protected abstract TClient Client { get; set; } + protected abstract string NsName { get; set; } + protected abstract string DbPath { get; set; } + protected virtual StorageEngine Storage => StorageEngine.LevelDb; + + public TestContext TestContext { get; set; } + + public BaseTest(bool testModifedItemCount, bool testPrecepts) { - private readonly bool _testModifiedItemCount; - private readonly bool _testPrecepts; + _testModifiedItemCount = testModifedItemCount; + _testPrecepts = testPrecepts; + } - protected abstract TClient Client { get; set; } - protected abstract string NsName { get; set; } - protected abstract string DbPath { get; set; } - protected virtual StorageEngine Storage => StorageEngine.LevelDb; + [TestMethod()] + public async Task PingAsyncTest() + { + await Client.PingAsync(); + } - public TestContext TestContext { get; set; } + [TestMethod()] + public async Task OpenDropEnumNamespaceTest() + { + await Client.OpenNamespaceAsync(nameof(OpenDropEnumNamespaceTest), new NamespaceOptions { CreateIfMissing = true }); + var namespaces = await Client.EnumNamespacesAsync(); + CollectionAssert.Contains(namespaces.Select(ns => ns.Name).ToList(), nameof(OpenDropEnumNamespaceTest)); + await Client.DropNamespaceAsync(nameof(OpenDropEnumNamespaceTest)); + namespaces = await Client.EnumNamespacesAsync(); + CollectionAssert.DoesNotContain(namespaces.Select(ns => ns.Name).ToList(), nameof(OpenDropEnumNamespaceTest)); + } - public BaseTest(bool testModifedItemCount, bool testPrecepts) + private async Task AddIndexesAsync() + { + foreach (var index in new[]{ + new Index + { + Name = "Id", + IsPk = true, + FieldType = FieldType.Int64, + IsDense = true, + IndexType = IndexType.Hash + }, + new Index + { + Name = "Name", + IsDense = true, + FieldType = FieldType.String, + IndexType = IndexType.Hash + }, + new Index + { + Name = "ArrayIndex", + IsArray = true, + IsDense = true, + FieldType = FieldType.String, + IndexType = IndexType.Hash + }, + new Index + { + Name = "RangeIndex", + FieldType = FieldType.Double, + IndexType = IndexType.Tree + }, + new Index + { + Name = "SerialPrecept", + FieldType = FieldType.Int, + IndexType = IndexType.Tree + }, + new Index + { + Name = "UpdateTime", + FieldType = FieldType.Int64, + IndexType = IndexType.Tree + } + }) { - _testModifiedItemCount = testModifedItemCount; - _testPrecepts = testPrecepts; - } + await Client.AddIndexAsync(NsName, index); + } + + var nsInfo = (await Client.ExecuteSqlAsync($"SELECT * FROM #namespaces WHERE name='{NsName}' LIMIT 1")).Items.FirstOrDefault(); + Assert.IsNotNull(nsInfo); + Assert.AreEqual(NsName, nsInfo.Name); + var indexNames = nsInfo.Indexes.Select(i => i.Name).ToList(); + CollectionAssert.Contains(indexNames, "Id"); + CollectionAssert.Contains(indexNames, "Name"); + CollectionAssert.Contains(indexNames, "ArrayIndex"); + CollectionAssert.Contains(indexNames, "RangeIndex"); + } - [TestMethod()] - public async Task PingAsyncTest() - { - await Client.PingAsync(); - } + public class TestDocument + { + public long Id { get; set; } + public string Name { get; set; } + public string[] ArrayIndex { get; set; } + public double RangeIndex { get; set; } + public byte[] Payload { get; set; } + public int SerialPrecept { get; set; } //precepts cant be null + public long UpdateTime { get; set; } //precepts cant be null + public string NullablePayload { get; set; } + public int? NullableIntPayload { get; set; } + } - [TestMethod()] - public async Task OpenDropEnumNamespaceTest() - { - await Client.OpenNamespaceAsync(nameof(OpenDropEnumNamespaceTest), new NamespaceOptions { CreateIfMissing = true }); - var namespaces = await Client.EnumNamespacesAsync(); - CollectionAssert.Contains(namespaces.Select(ns => ns.Name).ToList(), nameof(OpenDropEnumNamespaceTest)); - await Client.DropNamespaceAsync(nameof(OpenDropEnumNamespaceTest)); - namespaces = await Client.EnumNamespacesAsync(); - CollectionAssert.DoesNotContain(namespaces.Select(ns => ns.Name).ToList(), nameof(OpenDropEnumNamespaceTest)); - } + private async Task AddItemsAsync(int idStart, int idEnd) + { + var insertedItemCount = await Client.UpsertAsync(NsName, Enumerable.Range(idStart, idEnd - idStart).Select(i => + new TestDocument + { + Id = i, + Name = $"Name of {i}", + ArrayIndex = Enumerable.Range(0, i).Select(r => $"..{r}..").ToArray(), + RangeIndex = i * 0.125, + Payload = Enumerable.Range(0, i).Select(r => (byte)(r % 255)).ToArray(), + NullablePayload = i % 2 == 0 ? i.ToString() : null, + NullableIntPayload = i % 2 == 0 ? i : (int?)null + })); + + + Assert.AreEqual(idEnd - idStart, insertedItemCount); + } - private async Task AddIndexesAsync() + [TestMethod] + public async Task ErrorTest() + { + var excp = await Assert.ThrowsExceptionAsync(async () => { - foreach (var index in new[]{ - new Index - { - Name = "Id", - IsPk = true, - FieldType = FieldType.Int64, - IsDense = true, - IndexType = IndexType.Hash - }, - new Index - { - Name = "Name", - IsDense = true, - FieldType = FieldType.String, - IndexType = IndexType.Hash - }, - new Index - { - Name = "ArrayIndex", - IsArray = true, - IsDense = true, - FieldType = FieldType.String, - IndexType = IndexType.Hash - }, - new Index - { - Name = "RangeIndex", - FieldType = FieldType.Double, - IndexType = IndexType.Tree - }, - new Index - { - Name = "SerialPrecept", - FieldType = FieldType.Int, - IndexType = IndexType.Tree - }, - new Index - { - Name = "UpdateTime", - FieldType = FieldType.Int64, - IndexType = IndexType.Tree - } - }) - { - await Client.AddIndexAsync(NsName, index); - } - - var nsInfo = (await Client.ExecuteSqlAsync($"SELECT * FROM #namespaces WHERE name='{NsName}' LIMIT 1")).Items.FirstOrDefault(); - Assert.IsNotNull(nsInfo); - Assert.AreEqual(NsName, nsInfo.Name); - var indexNames = nsInfo.Indexes.Select(i => i.Name).ToList(); - CollectionAssert.Contains(indexNames, "Id"); - CollectionAssert.Contains(indexNames, "Name"); - CollectionAssert.Contains(indexNames, "ArrayIndex"); - CollectionAssert.Contains(indexNames, "RangeIndex"); - } + await Client.DropNamespaceAsync(Guid.NewGuid().ToString()); + }); - public class TestDocument - { - public long Id { get; set; } - public string Name { get; set; } - public string[] ArrayIndex { get; set; } - public double RangeIndex { get; set; } - public byte[] Payload { get; set; } - public int SerialPrecept { get; set; } //precepts cant be null - public long UpdateTime { get; set; } //precepts cant be null - public string NullablePayload { get; set; } - public int? NullableIntPayload { get; set; } - } + Assert.AreNotEqual(ReindexerErrorCode.OK, excp?.ErrorCode); + } - private async Task AddItemsAsync(int idStart, int idEnd) - { - var insertedItemCount = await Client.UpsertAsync(NsName, Enumerable.Range(idStart, idEnd - idStart).Select(i => - new TestDocument - { - Id = i, - Name = $"Name of {i}", - ArrayIndex = Enumerable.Range(0, i).Select(r => $"..{r}..").ToArray(), - RangeIndex = i * 0.125, - Payload = Enumerable.Range(0, i).Select(r => (byte)(r % 255)).ToArray(), - NullablePayload = i % 2 == 0 ? i.ToString() : null, - NullableIntPayload = i % 2 == 0 ? i : (int?)null - })); - - - Assert.AreEqual(idEnd - idStart, insertedItemCount); - } + [TestMethod] + public virtual async Task ExecuteQueryJson() + { + await AddIndexesAsync(); + + await AddItemsAsync(0, 1000); - [TestMethod] - public async Task ErrorTest() + var docs = await Client.ExecuteAsync(Encoding.UTF8.GetBytes($$""" { - var excp = await Assert.ThrowsExceptionAsync(async () => + "namespace": "{{NsName}}", + "req_total": "enabled", + "filters": [ { - await Client.DropNamespaceAsync(Guid.NewGuid().ToString()); - }); + "field": "Id", + "cond": "LT", + "value": [ + "1000" + ] + } + ], + "explain": false, + "select_filter": [], + "strict_mode": "names" + } + """), SerializerType.Json); + Assert.AreEqual(1000, docs.QueryTotalItems); + var item = docs.Items.FirstOrDefault(i => i.Id == 2); + Assert.AreEqual($"Name of 2", item.Name); + CollectionAssert.AreEqual(new[] { "..0..", "..1.." }, item.ArrayIndex); + Assert.AreEqual(2 * 0.125, item.RangeIndex); + CollectionAssert.AreEqual(Enumerable.Range(0, 2).Select(r => (byte)(r % 255)).ToArray(), item.Payload); + } - Assert.AreNotEqual(ReindexerErrorCode.OK, excp?.ErrorCode); - } + [TestMethod] + public async Task ExecuteSql() + { + await AddIndexesAsync(); - [TestMethod] - public async Task ExecuteSql() - { - await AddIndexesAsync(); + await AddItemsAsync(0, 1000); - await AddItemsAsync(0, 1000); + var docs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id < 1000"); + Assert.AreEqual(1000, docs.QueryTotalItems); + var item = docs.Items.FirstOrDefault(i => i.Id == 2); + Assert.AreEqual($"Name of 2", item.Name); + CollectionAssert.AreEqual(new[] { "..0..", "..1.." }, item.ArrayIndex); + Assert.AreEqual(2 * 0.125, item.RangeIndex); + CollectionAssert.AreEqual(Enumerable.Range(0, 2).Select(r => (byte)(r % 255)).ToArray(), item.Payload); - var docs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id < 1000"); - Assert.AreEqual(1000, docs.QueryTotalItems); - var item = docs.Items.FirstOrDefault(i => i.Id == 2); - Assert.AreEqual($"Name of 2", item.Name); - CollectionAssert.AreEqual(new[] { "..0..", "..1.." }, item.ArrayIndex); - Assert.AreEqual(2 * 0.125, item.RangeIndex); - CollectionAssert.AreEqual(Enumerable.Range(0, 2).Select(r => (byte)(r % 255)).ToArray(), item.Payload); + var arrayContainsDocs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE ArrayIndex IN ('..997..','..998..')"); + Assert.AreEqual(2, arrayContainsDocs.QueryTotalItems); - var arrayContainsDocs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE ArrayIndex IN ('..997..','..998..')"); - Assert.AreEqual(2, arrayContainsDocs.QueryTotalItems); + var rangeQueryDocs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE RangeIndex > 5.1 AND RangeIndex < 6"); + Assert.AreEqual(5.125, rangeQueryDocs.Items.FirstOrDefault()?.RangeIndex); - var rangeQueryDocs = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE RangeIndex > 5.1 AND RangeIndex < 6"); - Assert.AreEqual(5.125, rangeQueryDocs.Items.FirstOrDefault()?.RangeIndex); + var deletedCount = await Client.DeleteAsync(NsName, new[] { new TestDocument { Id = 500 } }); + Assert.AreEqual(1, deletedCount); + var deletedQ = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=500"); + Assert.AreEqual(0, deletedQ.QueryTotalItems); - var deletedCount = await Client.DeleteAsync(NsName, new[] { new TestDocument { Id = 500 } }); - Assert.AreEqual(1, deletedCount); - var deletedQ = await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=500"); - Assert.AreEqual(0, deletedQ.QueryTotalItems); + var notExistDeletedCount = await Client.DeleteAsync(NsName, new[] { new TestDocument { Id = 500 } }); + if (_testModifiedItemCount)//Grpc api doesn't support modified item count, only handled count returned. + Assert.AreEqual(0, notExistDeletedCount); - var notExistDeletedCount = await Client.DeleteAsync(NsName, new[] { new TestDocument { Id = 500 } }); - if (_testModifiedItemCount)//Grpc api doesn't support modified item count, only handled count returned. - Assert.AreEqual(0, notExistDeletedCount); + var multipleDeletedCount = await Client.ExecuteSqlAsync($"DELETE FROM {NsName} WHERE Id > 501"); + Assert.AreEqual(498, multipleDeletedCount.QueryTotalItems); - var multipleDeletedCount = await Client.ExecuteSqlAsync($"DELETE FROM {NsName} WHERE Id > 501"); - Assert.AreEqual(498, multipleDeletedCount.QueryTotalItems); + if (_testPrecepts) + { + var preceptQueryCount = await Client.UpdateAsync(NsName, new[] { new TestDocument { Id = 1, Name = "Updated" } }, + precepts: new[] { "SerialPrecept=SERIAL()", "UpdateTime=NOW(msec)" }); + Assert.AreEqual(1, preceptQueryCount); + var preceptQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=1")).Items.First(); + Assert.AreEqual("Updated", preceptQ.Name); + Assert.AreNotEqual(0, preceptQ.SerialPrecept); + Assert.AreNotEqual(0, preceptQ.UpdateTime); + } - if (_testPrecepts) - { - var preceptQueryCount = await Client.UpdateAsync(NsName, new[] { new TestDocument { Id = 1, Name = "Updated" } }, - precepts: new[] { "SerialPrecept=SERIAL()", "UpdateTime=NOW(msec)" }); - Assert.AreEqual(1, preceptQueryCount); - var preceptQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=1")).Items.First(); - Assert.AreEqual("Updated", preceptQ.Name); - Assert.AreNotEqual(0, preceptQ.SerialPrecept); - Assert.AreNotEqual(0, preceptQ.UpdateTime); - } + var nullableStringQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=100")).Items.First(); + Assert.AreEqual("100", nullableStringQ.NullablePayload); + var nullableStringQ2 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=101")).Items.First(); + Assert.AreEqual(null, nullableStringQ2.NullablePayload); - var nullableStringQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=100")).Items.First(); - Assert.AreEqual("100", nullableStringQ.NullablePayload); - var nullableStringQ2 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=101")).Items.First(); - Assert.AreEqual(null, nullableStringQ2.NullablePayload); + var nullableIntQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=100")).Items.First(); + Assert.AreEqual(100, nullableIntQ.NullableIntPayload); + var nullableIntQ2 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=101")).Items.First(); + Assert.AreEqual(null, nullableIntQ2.NullableIntPayload); + } + + [TestMethod] + public async Task Transaction() + { + await AddIndexesAsync(); - var nullableIntQ = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=100")).Items.First(); - Assert.AreEqual(100, nullableIntQ.NullableIntPayload); - var nullableIntQ2 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=101")).Items.First(); - Assert.AreEqual(null, nullableIntQ2.NullableIntPayload); + using (var tran = await Client.StartTransactionAsync(NsName)) + { + await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10500 } } ); + Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10500")).QueryTotalItems); + tran.Commit(); } + Assert.AreEqual(1, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10500")).QueryTotalItems); - [TestMethod] - public async Task Transaction() + using (var tran = await Client.StartTransactionAsync(NsName)) { - await AddIndexesAsync(); + await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10501 } } ); + await tran.RollbackAsync(); + } + Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10501")).QueryTotalItems); + try + { using (var tran = await Client.StartTransactionAsync(NsName)) { - await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10500 } } ); - Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10500")).QueryTotalItems); - tran.Commit(); + await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10502 } }); + throw new Exception(); } - Assert.AreEqual(1, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10500")).QueryTotalItems); + } + catch + { + //ignored because testing rollback + } + Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10502")).QueryTotalItems); + } - using (var tran = await Client.StartTransactionAsync(NsName)) - { - await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10501 } } ); - await tran.RollbackAsync(); - } - Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10501")).QueryTotalItems); + [TestMethod] + public async Task Utf8Test() + { + await AddIndexesAsync(); - try - { - using (var tran = await Client.StartTransactionAsync(NsName)) + var utf8Str = "İŞĞÜÇÖışğüöç بِسْــــــــــــــــــــــمِ اﷲِارَّحْمَنِ ارَّحِيم"; + await Client.UpsertAsync(NsName, + new[]{new TestDocument { - await tran.ModifyItemsAsync(ItemModifyMode.Insert, new[] { new TestDocument { Id = 10502 } }); - throw new Exception(); - } - } - catch - { - //ignored because testing rollback - } - Assert.AreEqual(0, (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10502")).QueryTotalItems); - } + Id = 10001, + Name = utf8Str + } }); - [TestMethod] - public async Task Utf8Test() - { - await AddIndexesAsync(); + var itemWithPayloadUtf8 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10001")).Items.FirstOrDefault(); + Assert.AreEqual(utf8Str, itemWithPayloadUtf8?.Name); + + var itemWithQueryUtf8 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Name='{utf8Str}'")).Items.FirstOrDefault(); + Assert.AreEqual(utf8Str, itemWithQueryUtf8?.Name); + } + + [TestMethod] + public async Task QueryBuilderTest() + { + await AddIndexesAsync(); + + await AddItemsAsync(0, 1000); + + var docs = await Client.ExecuteAsync(NsName, + q => q.Where("Id", Condition.LT ,1000)); + Assert.AreEqual(1000, docs.QueryTotalItems); + var item = docs.Items.FirstOrDefault(i => i.Id == 2); + Assert.AreEqual($"Name of 2", item.Name); + CollectionAssert.AreEqual(new[] { "..0..", "..1.." }, item.ArrayIndex); + Assert.AreEqual(2 * 0.125, item.RangeIndex); + CollectionAssert.AreEqual(Enumerable.Range(0, 2).Select(r => (byte)(r % 255)).ToArray(), item.Payload); + + var arrayContainsDocs = await Client.ExecuteAsync(NsName, + q => q.WhereString("ArrayIndex", Condition.SET, "..997..","..998..")); + Assert.AreEqual(2, arrayContainsDocs.QueryTotalItems); + + var rangeQueryDocs = await Client.ExecuteAsync(NsName, + q => q.WhereDouble("RangeIndex", Condition.RANGE, 5.1d, 6d)); + Assert.AreEqual(5.125, rangeQueryDocs.Items.FirstOrDefault()?.RangeIndex); + + var deletedCount = await Client.DeleteAsync(NsName, new[] { new TestDocument { Id = 500 } }); + Assert.AreEqual(1, deletedCount); + var deletedQ = await Client.ExecuteAsync(NsName, + q => q.WhereInt("Id", Condition.EQ, 500)); + Assert.AreEqual(0, deletedQ.QueryTotalItems); - var utf8Str = "İŞĞÜÇÖışğüöç بِسْــــــــــــــــــــــمِ اﷲِارَّحْمَنِ ارَّحِيم"; - await Client.UpsertAsync(NsName, - new[]{new TestDocument - { - Id = 10001, - Name = utf8Str - } }); - var itemWithPayloadUtf8 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Id=10001")).Items.FirstOrDefault(); - Assert.AreEqual(utf8Str, itemWithPayloadUtf8?.Name); - var itemWithQueryUtf8 = (await Client.ExecuteSqlAsync($"SELECT * FROM {NsName} WHERE Name='{utf8Str}'")).Items.FirstOrDefault(); - Assert.AreEqual(utf8Str, itemWithQueryUtf8?.Name); - } } } \ No newline at end of file diff --git a/Tests/ReindexerNet.EmbeddedBenchmarks/SelectBenchmark.cs b/Tests/ReindexerNet.EmbeddedBenchmarks/SelectBenchmark.cs index be19408..fc0ba11 100644 --- a/Tests/ReindexerNet.EmbeddedBenchmarks/SelectBenchmark.cs +++ b/Tests/ReindexerNet.EmbeddedBenchmarks/SelectBenchmark.cs @@ -930,9 +930,11 @@ public void RealmClean() [Benchmark] public IList ReindexerNet_SelectArray() { - var result = new List(); - result.Add(_rxClient.ExecuteSql($"Select * FROM Entities WHERE IntArray IN ({N})")); - result.Add(_rxClient.ExecuteSql($"Select * FROM Entities WHERE StrArray IN ('{N}')")); + var result = new List + { + _rxClient.ExecuteSql($"Select * FROM Entities WHERE IntArray IN ({N})"), + _rxClient.ExecuteSql($"Select * FROM Entities WHERE StrArray IN ('{N}')") + }; return result; } @@ -940,9 +942,11 @@ public void RealmClean() [Benchmark] public IList ReindexerNetDense_SelectArray() { - var result = new List(); - result.Add(_rxClientDense.ExecuteSql($"Select * FROM Entities WHERE IntArray IN ({N})")); - result.Add(_rxClientDense.ExecuteSql($"Select * FROM Entities WHERE StrArray IN ('{N}')")); + var result = new List + { + _rxClientDense.ExecuteSql($"Select * FROM Entities WHERE IntArray IN ({N})"), + _rxClientDense.ExecuteSql($"Select * FROM Entities WHERE StrArray IN ('{N}')") + }; return result; } @@ -1005,9 +1009,11 @@ public void RealmClean() [Benchmark] public IList Realm_SelectArray() { - var result = new List(); - result.Add(_realm.All().Filter("ANY IntArray.@values == $0", N).ToList()); - result.Add(_realm.All().Filter("ANY StrArray.@values == '$0'", N.ToString()).ToList()); + var result = new List + { + _realm.All().Filter("ANY IntArray.@values == $0", N).ToList(), + _realm.All().Filter("ANY StrArray.@values == '$0'", N.ToString()).ToList() + }; return result; } } \ No newline at end of file diff --git a/Tests/ReindexerNet.EmbeddedTest/EmbeddedTest.cs b/Tests/ReindexerNet.EmbeddedTest/EmbeddedTest.cs index d094dd9..5fb2aeb 100644 --- a/Tests/ReindexerNet.EmbeddedTest/EmbeddedTest.cs +++ b/Tests/ReindexerNet.EmbeddedTest/EmbeddedTest.cs @@ -9,7 +9,7 @@ namespace ReindexerNet.EmbeddedTest; [TestCategory("LevelDb")] [TestClass] -public class EmbeddedTest: BaseTest +public class EmbeddedTest : BaseTest { public EmbeddedTest() : base(true, true) { @@ -28,7 +28,7 @@ public virtual async Task InitAsync() Directory.Delete(DbPath, true); Client = new ReindexerEmbedded(DbPath); ReindexerEmbedded.EnableLogger(Log); - await Client.ConnectAsync(new ConnectionOptions{ Engine = Storage }); + await Client.ConnectAsync(new ConnectionOptions { Engine = Storage }); await Client.OpenNamespaceAsync(NsName); await Client.TruncateNamespaceAsync(NsName); } @@ -47,4 +47,10 @@ public virtual void Cleanup() Directory.Delete(DbPath, true); } + [TestMethod] + public override async Task ExecuteQueryJson() + { + await Microsoft.VisualStudio.TestTools.UnitTesting.Assert.ThrowsExceptionAsync(base.ExecuteQueryJson, + "Reindexer returned an error response, ErrCode: 8, Msg:Unknown type 34 while parsing binary buffer"); + } } diff --git a/Tests/ReindexerNet.EmbeddedTest/ReindexerBindingTest.cs b/Tests/ReindexerNet.EmbeddedTest/ReindexerBindingTest.cs index 9e9159a..7891701 100644 --- a/Tests/ReindexerNet.EmbeddedTest/ReindexerBindingTest.cs +++ b/Tests/ReindexerNet.EmbeddedTest/ReindexerBindingTest.cs @@ -13,6 +13,7 @@ using System.Threading; using System.Threading.Tasks; using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; +using ReindexerNet.Internal; namespace ReindexerNet.EmbeddedTest; @@ -68,13 +69,13 @@ public void Connect() { AssertError( ReindexerBinding.reindexer_connect(_rx, - $"builtin://{Path.Combine(Path.GetTempPath(), "ReindexerBindingTest")}".GetHandle(), + $"builtin://{Path.Combine(Path.GetTempPath(), "ReindexerBindingTest")}".GetStringHandle(), new ConnectOpts { options = ConnectOpt.kConnectOptAllowNamespaceErrors, storage = StorageTypeOpt.kStorageTypeOptLevelDB, expectedClusterID = 0 - }, ReindexerBinding.ReindexerVersion.GetHandle())); + }, ReindexerBinding.ReindexerVersion.GetStringHandle())); } [TestMethod] @@ -89,7 +90,7 @@ public void EnableStorage() var dbPath = Path.Combine(Path.GetTempPath(), "TestDbForEnableStorage"); if (Directory.Exists(dbPath)) Directory.Delete(dbPath, true); - AssertError(ReindexerBinding.reindexer_enable_storage(ReindexerBinding.init_reindexer(), dbPath.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_enable_storage(ReindexerBinding.init_reindexer(), dbPath.GetStringHandle(), _ctxInfo)); } [TestMethod] @@ -101,21 +102,21 @@ public void InitSystemNamespaces() [TestMethod] public void OpenNamespace() { - AssertError(ReindexerBinding.reindexer_open_namespace(_rx, "OpenNamespaceTest".GetHandle(), + AssertError(ReindexerBinding.reindexer_open_namespace(_rx, "OpenNamespaceTest".GetStringHandle(), new StorageOpts { options = StorageOpt.kStorageOptCreateIfMissing | StorageOpt.kStorageOptEnabled }, _ctxInfo)); - AssertError(ReindexerBinding.reindexer_drop_namespace(_rx, "OpenNamespaceTest".GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_drop_namespace(_rx, "OpenNamespaceTest".GetStringHandle(), _ctxInfo)); } [TestMethod] public void DropNamespace() { - var error = ReindexerBinding.reindexer_drop_namespace(_rx, "DropNamespaceTest".GetHandle(), _ctxInfo); + var error = ReindexerBinding.reindexer_drop_namespace(_rx, "DropNamespaceTest".GetStringHandle(), _ctxInfo); Assert.AreNotEqual(0, error.code); - AssertError(ReindexerBinding.reindexer_open_namespace(_rx, "DropNamespaceTest".GetHandle(), + AssertError(ReindexerBinding.reindexer_open_namespace(_rx, "DropNamespaceTest".GetStringHandle(), new StorageOpts { options = StorageOpt.kStorageOptCreateIfMissing | StorageOpt.kStorageOptEnabled }, _ctxInfo)); - AssertError(ReindexerBinding.reindexer_drop_namespace(_rx, "DropNamespaceTest".GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_drop_namespace(_rx, "DropNamespaceTest".GetStringHandle(), _ctxInfo)); } #if NET472 @@ -138,7 +139,7 @@ public void DropNamespace() public void ModifyItemPacked(string itemJson = null) { - AssertError(ReindexerBinding.reindexer_open_namespace(_rx, DataTestNamespace.GetHandle(), + AssertError(ReindexerBinding.reindexer_open_namespace(_rx, DataTestNamespace.GetStringHandle(), new StorageOpts { options = StorageOpt.kStorageOptCreateIfMissing | StorageOpt.kStorageOptEnabled }, _ctxInfo)); @@ -149,9 +150,9 @@ public void ModifyItemPacked(string itemJson = null) IsPk = true, FieldType = FieldType.Int64, IndexType = IndexType.Hash, - JsonPaths = new List { "Id" } + JsonPaths = ["Id"] }, _jsonSerializerOptions); - AssertError(ReindexerBinding.reindexer_add_index(_rx, DataTestNamespace.GetHandle(), indexDefJson.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_add_index(_rx, DataTestNamespace.GetStringHandle(), indexDefJson.GetStringHandle(), _ctxInfo)); indexDefJson = JsonSerializer.Serialize( new Index { @@ -159,13 +160,13 @@ public void ModifyItemPacked(string itemJson = null) IsPk = false, FieldType = FieldType.String, IndexType = IndexType.Hash, - JsonPaths = new List { "Guid" } + JsonPaths = ["Guid"] }, _jsonSerializerOptions); - AssertError(ReindexerBinding.reindexer_add_index(_rx, DataTestNamespace.GetHandle(), indexDefJson.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_add_index(_rx, DataTestNamespace.GetStringHandle(), indexDefJson.GetStringHandle(), _ctxInfo)); var rsp = ReindexerBinding.reindexer_select(_rx, - $"SELECT 'indexes.name' FROM #namespaces WHERE name = '{DataTestNamespace}'".GetHandle(), - 1, new int[0], 0, _ctxInfo); + $"SELECT 'indexes.name' FROM #namespaces WHERE name = '{DataTestNamespace}'".GetStringHandle(), + 1, [], 0, _ctxInfo); if (rsp.err_code != 0) Assert.AreEqual(null, (string)rsp.@out); @@ -217,8 +218,8 @@ public void SelectSql() ModifyItemPacked(); var rsp = ReindexerBinding.reindexer_select(_rx, - $"SELECT * FROM {DataTestNamespace}".GetHandle(), - 1, new int[0], 0, _ctxInfo); + $"SELECT * FROM {DataTestNamespace}".GetStringHandle(), + 1, [], 0, _ctxInfo); if (rsp.err_code != 0) Assert.AreEqual(null, (string)rsp.@out); Assert.AreNotEqual(UIntPtr.Zero, rsp.@out.results_ptr); @@ -230,7 +231,7 @@ public void SelectSql() } finally { - AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetStringHandle(), _ctxInfo)); } } @@ -242,8 +243,8 @@ public void ExplainSelectSql() ModifyItemPacked(); var rsp = ReindexerBinding.reindexer_select(_rx, - $"EXPLAIN SELECT * FROM {DataTestNamespace}".GetHandle(), - 1, new int[0], 0, _ctxInfo); + $"EXPLAIN SELECT * FROM {DataTestNamespace}".GetStringHandle(), + 1, [], 0, _ctxInfo); if (rsp.err_code != 0) Assert.AreEqual(null, (string)rsp.@out); Assert.AreNotEqual(UIntPtr.Zero, rsp.@out.results_ptr); @@ -259,14 +260,14 @@ public void ExplainSelectSql() } finally { - AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetStringHandle(), _ctxInfo)); } } [TestMethod] public void DeleteSql() { - AssertError(ReindexerBinding.reindexer_open_namespace(_rx, DataTestNamespace.GetHandle(), + AssertError(ReindexerBinding.reindexer_open_namespace(_rx, DataTestNamespace.GetStringHandle(), new StorageOpts { options = StorageOpt.kStorageOptCreateIfMissing | StorageOpt.kStorageOptEnabled }, _ctxInfo)); @@ -275,8 +276,8 @@ public void DeleteSql() ModifyItemPacked($"{{\"Id\":2, \"Guid\":\"{Guid.NewGuid()}\"}}"); var delRsp = ReindexerBinding.reindexer_select(_rx, - $"DELETE FROM {DataTestNamespace} WHERE Id=2".GetHandle(), - 1, new int[0], 0, _ctxInfo); + $"DELETE FROM {DataTestNamespace} WHERE Id=2".GetStringHandle(), + 1, [], 0, _ctxInfo); if (delRsp.err_code != 0) Assert.AreEqual(null, (string)delRsp.@out); Assert.AreNotEqual(UIntPtr.Zero, delRsp.@out.results_ptr); @@ -286,7 +287,7 @@ public void DeleteSql() Assert.AreNotEqual(0, offsets.Count); var selRsp = ReindexerBinding.reindexer_select(_rx, - $"SELECT * FROM {DataTestNamespace} WHERE Id=2".GetHandle(), + $"SELECT * FROM {DataTestNamespace} WHERE Id=2".GetStringHandle(), 1, new int[] { 0 }, 1, _ctxInfo); if (selRsp.err_code != 0) Assert.AreEqual(null, (string)selRsp.@out); @@ -298,7 +299,7 @@ public void DeleteSql() } finally { - AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_close_namespace(_rx, DataTestNamespace.GetStringHandle(), _ctxInfo)); } } @@ -307,7 +308,7 @@ public void DeleteSql() public void ParallelModifyItemPacked() { var nsName = "ParallelTestNs"; - AssertError(ReindexerBinding.reindexer_open_namespace(_rx, nsName.GetHandle(), + AssertError(ReindexerBinding.reindexer_open_namespace(_rx, nsName.GetStringHandle(), new StorageOpts { options = StorageOpt.kStorageOptCreateIfMissing | StorageOpt.kStorageOptEnabled }, _ctxInfo)); @@ -318,9 +319,9 @@ public void ParallelModifyItemPacked() IsPk = true, FieldType = FieldType.Int64, IndexType = IndexType.Hash, - JsonPaths = new List { "Id" } + JsonPaths = ["Id"] }, _jsonSerializerOptions); - AssertError(ReindexerBinding.reindexer_add_index(_rx, nsName.GetHandle(), indexDefJson.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_add_index(_rx, nsName.GetStringHandle(), indexDefJson.GetStringHandle(), _ctxInfo)); using (var ser1 = new CJsonWriter()) { @@ -366,6 +367,6 @@ public void ParallelModifyItemPacked() GC.Collect(); GC.WaitForPendingFinalizers(); #pragma warning restore S1215 // "GC.Collect" should not be called - AssertError(ReindexerBinding.reindexer_truncate_namespace(_rx, nsName.GetHandle(), _ctxInfo)); + AssertError(ReindexerBinding.reindexer_truncate_namespace(_rx, nsName.GetStringHandle(), _ctxInfo)); } } diff --git a/src/ReindexerNet.Core/CJsonQueryBuilder.cs b/src/ReindexerNet.Core/CJsonQueryBuilder.cs new file mode 100644 index 0000000..12399da --- /dev/null +++ b/src/ReindexerNet.Core/CJsonQueryBuilder.cs @@ -0,0 +1,995 @@ +using ReindexerNet.Internal; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace ReindexerNet; + +public enum QueryStrictMode +{ + QueryStrictModeNotSet = 0, + QueryStrictModeNone = 1, + QueryStrictModeNames = 2, + QueryStrictModeIndexes = 3 +} + +/// +/// Builds CJson query from given query. +/// +public sealed class CJsonQueryBuilder : IQueryBuilder, IUpdateQueryBuilder, ISerializableQueryBuilder +{ + private readonly ReindexerJsonSerializer _jsonSerializer; + private Bindings.Op _nextOp = Bindings.Op.And; + private readonly CJsonWriter _ser = new(); + private CJsonQueryBuilder _root; + private readonly List _joinQueries = []; + private readonly List _mergedQueries = []; + private Bindings.JoinType _joinType = 0; + private bool _closed; + private int _queriesCount; + private readonly List _opennedBrackets = []; + private readonly List _traceNew = []; + private readonly List _traceClose = []; + + /// + public string TotalName { get; set; } = string.Empty; + /// + public int FetchCount { get; set; } = 1000; + + /// + /// Builds CJson query from given query. + /// + /// + /// + public CJsonQueryBuilder(ReindexerJsonSerializer jsonSerializer, string @namespace) + { + _jsonSerializer = jsonSerializer; + _ser.PutVString(@namespace); + } + + /// + public IQueryBuilder Where(string index, Condition condition, object keys) + { + Type t = keys.GetType(); + List keysList = []; + if (t.IsArray) + { + keysList = ((Array)keys).Cast().ToList(); + } + else if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>)) + { + keysList = ((List)keys).ToList(); + } + else + { + keysList.Add(keys); + } + + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keysList.Count); + foreach (object key in keysList) + { + PutValue(key); + } + + return this; + } + + /// + public IQueryBuilder WhereBetweenFields(string firstField, Condition condition, string secondField) + { + _ser.PutVarCUInt((int)Bindings.Query.BetweenFieldsCondition); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVString(firstField); + _ser.PutVarCUInt((int)condition); + _ser.PutVString(secondField); + _nextOp = Bindings.Op.And; + _queriesCount++; + return this; + } + + public IQueryBuilder OpenBracket() + { + _ser.PutVarCUInt((int)Bindings.Query.OpenBracket); + _ser.PutVarCUInt((int)_nextOp); + _nextOp = Bindings.Op.And; + _opennedBrackets.Add(_queriesCount); + _queriesCount++; + return this; + } + + public IQueryBuilder CloseBracket() + { + if (_nextOp != Bindings.Op.And) + { + throw new ReindexerNetException("Operation before close bracket"); + } + if (_opennedBrackets.Count < 1) + { + throw new ReindexerNetException("Close bracket before open it"); + } + _ser.PutVarCUInt((int)Bindings.Query.CloseBracket); + _opennedBrackets.RemoveAt(_opennedBrackets.Count - 1); + return this; + } + + private void PutValue(object value) + { + var t = value.GetType(); + if (value is bool b) + { + _ser.PutVarCUInt((int)Bindings.Value.Bool); + _ser.PutVarUInt(b ? 1u : 0u); + } + else if (value is int i) + { + _ser.PutVarCUInt((int)Bindings.Value.Int); + _ser.PutVarInt(i); + } + else if (value is long l) + { + _ser.PutVarCUInt((int)Bindings.Value.Int64); + _ser.PutVarInt(l); + } + else if (value is string s) + { + _ser.PutVarCUInt((int)Bindings.Value.String); + _ser.PutVString(s); + } + else if (value is double d) + { + _ser.PutVarCUInt((int)Bindings.Value.Double); + _ser.PutDouble(d); + } + else if (value is ICollection c) + { + _ser.PutVarCUInt((int)Bindings.Value.Tuple); + _ser.PutVarCUInt(c.Count); + foreach (object val in c) + { + PutValue(val); + } + } + else + { + throw new ReindexerNetException("Invalid reflection type " + t.Name.ToString()); + } + } + + /// + public IQueryBuilder Where(Action filterQuery) + { + OpenBracket(); + + filterQuery(this); + + CloseBracket(); + return this; + } + + /// + public IQueryBuilder WhereInt(string index, Condition condition, params int[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (int key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.Int); + _ser.PutVarInt(key); + } + return this; + } + + /// + public IQueryBuilder WhereInt32(string index, Condition condition, params int[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (int key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.Int); + _ser.PutVarInt(key); + } + return this; + } + + /// + public IQueryBuilder WhereInt64(string index, Condition condition, params long[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (long key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.Int64); + _ser.PutVarInt(key); + } + return this; + } + + /// + public IQueryBuilder WhereString(string index, Condition condition, params string[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (string key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.String); + _ser.PutVString(key); + } + return this; + } + + /// + public IQueryBuilder WhereUuid(string index, Condition condition, params string[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (string key in keys) + { + if (Guid.TryParse(key, out Guid uuid)) + { + _ser.PutVarCUInt((int)Bindings.Value.Uuid); + _ser.PutUuid(uuid); + } + else + { + _ser.PutVarCUInt((int)Bindings.Value.String); + _ser.PutVString(key); + } + } + return this; + } + + /// + public IQueryBuilder WhereComposite(string index, Condition condition, params object[] keys) + { + return Where(index, condition, keys); + } + + /// + public IQueryBuilder Match(string index, params string[] keys) + { + return WhereString(index, Condition.EQ, keys); + } + + /// + public IQueryBuilder WhereBool(string index, Condition condition, params bool[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (bool key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.Bool); + _ser.PutVarUInt(key ? 1u : 0u); + } + return this; + } + + /// + public IQueryBuilder WhereDouble(string index, Condition condition, params double[] keys) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(keys.Length); + foreach (double key in keys) + { + _ser.PutVarCUInt((int)Bindings.Value.Double); + _ser.PutDouble(key); + } + return this; + } + + /// + public IQueryBuilder DWithin(string index, (double start, double end) point, double distance) + { + _ser.PutVarCUInt((int)Bindings.Query.Condition); + _ser.PutVString(index); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)Condition.DWITHIN); + _nextOp = Bindings.Op.And; + _queriesCount++; + _ser.PutVarCUInt(3); + _ser.PutVarCUInt((int)Bindings.Value.Double); + _ser.PutDouble(point.start); + _ser.PutVarCUInt((int)Bindings.Value.Double); + _ser.PutDouble(point.end); + _ser.PutVarCUInt((int)Bindings.Value.Double); + _ser.PutDouble(distance); + return this; + } + + /// + public IQueryBuilder AggregateSum(string field) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Sum); + _ser.PutVarCUInt(1); + _ser.PutVString(field); + return this; + } + + /// + public IQueryBuilder AggregateAvg(string field) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Avg); + _ser.PutVarCUInt(1); + _ser.PutVString(field); + return this; + } + + /// + public IQueryBuilder AggregateMin(string field) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Min); + _ser.PutVarCUInt(1); + _ser.PutVString(field); + return this; + } + + /// + public IQueryBuilder AggregateMax(string field) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Max); + _ser.PutVarCUInt(1); + _ser.PutVString(field); + return this; + } + + private sealed class AggregateFacetRequest : IAggregateFacetRequest + { + private readonly CJsonQueryBuilder query; + + public AggregateFacetRequest(CJsonQueryBuilder query) + { + this.query = query; + } + + public IAggregateFacetRequest Limit(int limit) + { + query._ser.PutVarCUInt((int)Bindings.Query.AggregationLimit); + query._ser.PutVarCUInt(limit); + return this; + } + + public IAggregateFacetRequest Offset(int offset) + { + query._ser.PutVarCUInt((int)Bindings.Query.AggregationOffset); + query._ser.PutVarCUInt(offset); + return this; + } + + public IAggregateFacetRequest Sort(string field, bool desc) + { + query._ser.PutVarCUInt((int)Bindings.Query.AggregationSort); + query._ser.PutVString(field); + if (desc) + { + query._ser.PutVarCUInt(1); + } + else + { + query._ser.PutVarCUInt(0); + } + return this; + } + } + + /// + public IQueryBuilder AggregateFacet(Action aggFacetQuery, params string[] fields) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Facet); + _ser.PutVarCUInt(fields.Length); + foreach (string field in fields) + { + _ser.PutVString(field); + } + aggFacetQuery(new AggregateFacetRequest(this)); + return this; + } + + /// + public IQueryBuilder Sort(string sortIndex, bool desc, params object[] values) + { + _ser.PutVarCUInt((int)Bindings.Query.SortIndex); + _ser.PutVString(sortIndex); + if (desc) + { + _ser.PutVarUInt(1); + } + else + { + _ser.PutVarUInt(0); + } + _ser.PutVarCUInt(values.Length); + for (int i = 0; i < values.Length; i++) + { + PutValue(values[i]); + } + return this; + } + + /// + public IQueryBuilder SortStPointDistance(string field, (double X, double Y) p, bool desc) + { + var sb = new StringBuilder(); + sb.Append("ST_Distance(") + .Append(field) + .Append(",ST_GeomFromText('point(") + .Append(p.X.ToString("f", System.Globalization.CultureInfo.InvariantCulture)) + .Append(' ') + .Append(p.Y.ToString("f", System.Globalization.CultureInfo.InvariantCulture)) + .Append(")'))"); + + var expression = sb.ToString(); + return Sort(expression, desc); + } + + /// + public IQueryBuilder SortStFieldDistance(string field1, string field2, bool desc) + { + var sb = new StringBuilder(); + sb.Append("ST_Distance(") + .Append(field1) + .Append(',') + .Append(field2) + .Append(')'); + return Sort(sb.ToString(), desc); + } + + /// + public IQueryBuilder And() + { + _nextOp = Bindings.Op.And; + return this; + } + + /// + public IQueryBuilder Or() + { + _nextOp = Bindings.Op.Or; + return this; + } + + /// + public IQueryBuilder Not() + { + _nextOp = Bindings.Op.Not; + return this; + } + + /// + public IQueryBuilder Distinct(string distinctIndex) + { + _ser.PutVarCUInt((int)Bindings.Query.Aggregation); + _ser.PutVarCUInt((int)Bindings.Agg.Distinct); + _ser.PutVarCUInt(1); + _ser.PutVString(distinctIndex); + return this; + } + + /// + public IQueryBuilder ReqTotal(params string[] totalNames) + { + _ser.PutVarCUInt((int)Bindings.Query.ReqTotal); + _ser.PutVarCUInt((int)Bindings.TotalMode.AccurateTotal); + if (totalNames.Length != 0) + { + TotalName = totalNames[0]; + } + return this; + } + + /// + public IQueryBuilder CachedTotal(params string[] totalNames) + { + _ser.PutVarCUInt((int)Bindings.Query.ReqTotal); + _ser.PutVarCUInt((int)Bindings.TotalMode.CachedTotal); + if (totalNames.Length != 0) + { + TotalName = totalNames[0]; + } + return this; + } + + /// + public IQueryBuilder Limit(int limitItems) + { + if (limitItems > Bindings.CInt32Max) + { + limitItems = Bindings.CInt32Max; + } + _ser.PutVarCUInt((int)Bindings.Query.Limit); + _ser.PutVarCUInt(limitItems); + return this; + } + + /// + public IQueryBuilder Offset(int startOffset) + { + if (startOffset > Bindings.CInt32Max) + { + startOffset = Bindings.CInt32Max; + } + _ser.PutVarCUInt((int)Bindings.Query.Offset); + _ser.PutVarCUInt(startOffset); + return this; + } + + public IQueryBuilder Debug(int level) + { + _ser.PutVarCUInt((int)Bindings.Query.DebugLevel); + _ser.PutVarCUInt(level); + return this; + } + + /// + public IQueryBuilder Strict(QueryStrictMode mode) + { + _ser.PutVarCUInt((int)Bindings.Query.StrictMode); + _ser.PutVarCUInt((int)mode); + return this; + } + + /// + public IQueryBuilder Explain() + { + _ser.PutVarCUInt((int)Bindings.Query.Explain); + return this; + } + + /// + public IQueryBuilder WithRank() + { + _ser.PutVarCUInt((int)Bindings.Query.WithRank); + return this; + } + + private static bool enableDebug => Environment.GetEnvironmentVariable("ReindexerNet_Debug") != "1"; + + private static void mktrace(ref List buf) + { + if (enableDebug) + { + if (buf == null) + { + buf = []; + } + + StackTrace stackTrace = new StackTrace(false); + buf.AddRange(stackTrace.GetFrames()); + } + } + + private void panicTrace(string msg) + { + if (Environment.GetEnvironmentVariable("ReindexerNet_Debug") != "1") + { + Console.WriteLine("To see query allocation/close traces set ReindexerNet_Debug=1 environment variable!"); + } + else + { + Console.WriteLine("CJsonQueryBuilder allocation trace: " + _traceNew.ToString() + "\n\nQuery close trace " + _traceClose.ToString() + "\n\n"); + } + throw new ReindexerNetException(msg); + } + + private string getValueJSON(object? value) + { + bool ok = false; + ReadOnlySpan objectJSON = Array.Empty(); + Type t = value?.GetType(); + if (value == null) + { + objectJSON = Encoding.UTF8.GetBytes("{}"); + } + else if (t == typeof(object) || t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + objectJSON = _jsonSerializer.Serialize(value); + } + else if (!ok) + { + throw new ReindexerNetException("SetObject doesn't support this type of objects: " + t.Name.ToString()); + } + + return Encoding.UTF8.GetString(objectJSON +#if NETSTANDARD2_0 || NET472 + .ToArray() +#endif + ); + } + + /// + public IQueryBuilder SetObject(string field, object values) + { + int size = 1; + bool isArray = false; + var v = values as IList; + if (values is not byte[] && v != null) + { + size = v.Count; + isArray = true; + } + string[] jsonValues = new string[size]; + if (isArray) + { + for (int i = 0; i < size; i++) + { + jsonValues[i] = getValueJSON(v[i]); + } + } + else if (size > 0) + { + jsonValues[0] = getValueJSON(values); + } + _ser.PutVarCUInt((int)Bindings.Query.UpdateObject); + _ser.PutVString(field); + _ser.PutVarCUInt(size); + if (isArray) + { + _ser.PutVarCUInt(1); + } + else + { + _ser.PutVarCUInt(0); + } + for (int i = 0; i < size; i++) + { + _ser.PutVarUInt(0); + _ser.PutVarCUInt((int)Bindings.Value.String); + _ser.PutVString(jsonValues[i]); + } + return this; + } + + /// + public IQueryBuilder Set(string field, object values) + { + if (values != null) + { + Type t = values.GetType(); + if (t == typeof(object) || t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + return SetObject(field, values); + } + if (t.IsArray && !t.GetElementType().IsPrimitive && t.GetElementType() != typeof(string)) + { + return SetObject(field, values); + } + } + + var v = values as IList; + Bindings.Query cmd = Bindings.Query.UpdateField; + if (v != null && v.Count <= 1) + { + cmd = Bindings.Query.UpdateFieldV2; + } + _ser.PutVarCUInt((int)cmd); + _ser.PutVString(field); + if (values == null) + { + if (cmd == Bindings.Query.UpdateFieldV2) + { + _ser.PutVarUInt(0); + } + _ser.PutVarUInt(0); + } + else if (v != null) + { + if (cmd == Bindings.Query.UpdateFieldV2) + { + _ser.PutVarUInt(1); + } + _ser.PutVarCUInt(v.Count); + for (int i = 0; i < v.Count; i++) + { + _ser.PutVarUInt(0); + PutValue(v[i]); + } + } + else + { + if (cmd == Bindings.Query.UpdateFieldV2) + { + _ser.PutVarUInt(0); + } + _ser.PutVarCUInt(1); + _ser.PutVarUInt(0); + PutValue(v); + } + return this; + } + + /// + public IQueryBuilder Drop(string field) + { + _ser.PutVarCUInt((int)Bindings.Query.DropField); + _ser.PutVString(field); + return this; + } + + /// + public IQueryBuilder SetExpression(string field, string value) + { + _ser.PutVarCUInt((int)Bindings.Query.UpdateField); + _ser.PutVString(field); + _ser.PutVarCUInt(1); + _ser.PutVarUInt(1); + PutValue(value); + return this; + } + + private CJsonQueryBuilder join(CJsonQueryBuilder q2, string field, Bindings.JoinType joinType) + { + var query = this; + if (_root != null) + { + query = _root; + } + if (q2._root != null) + { + throw new Exception("this.Join call on already joined this. You should create new Query"); + } + if (joinType != Bindings.JoinType.LeftJoin) + { + _ser.PutVarCUInt((int)Bindings.Query.JoinCondition); + _ser.PutVarCUInt((int)joinType); + _ser.PutVarCUInt(_joinQueries.Count); + } + q2._joinType = joinType; + q2._root = query; + _joinQueries.Add(q2); + return this; + } + + /// + public IQueryBuilder InnerJoin(string otherNamespace, Action otherQuery, string field) + { + var otherQueryBuilder = new CJsonQueryBuilder(_jsonSerializer, otherNamespace); + otherQuery(otherQueryBuilder); + if (_nextOp == Bindings.Op.Or) + { + _nextOp = Bindings.Op.And; + return join(otherQueryBuilder, field, Bindings.JoinType.OrInnerJoin); + } + return join(otherQueryBuilder, field, Bindings.JoinType.InnerJoin); + } + + /// + public IQueryBuilder Join(string otherNamespace, Action otherQuery, string field) + { + return LeftJoin(otherNamespace, otherQuery, field); + } + + /// + public IQueryBuilder LeftJoin(string otherNamespace, Action otherQuery, string field) + { + var otherQueryBuilder = new CJsonQueryBuilder(_jsonSerializer, otherNamespace); + otherQuery(otherQueryBuilder); + return join(otherQueryBuilder, field, Bindings.JoinType.LeftJoin); + } + + /// + public IQueryBuilder Merge(string otherNamespace, Action otherQuery) + { + var otherQueryBuilder = new CJsonQueryBuilder(_jsonSerializer, otherNamespace); + otherQuery(otherQueryBuilder); + var query = this; + if (_root != null) + { + query = _root; + } + if (otherQueryBuilder._root != null) + { + otherQueryBuilder = otherQueryBuilder._root; + } + otherQueryBuilder._root = query; + _mergedQueries.Add(otherQueryBuilder); + return this; + } + + /// + public IQueryBuilder On(string index, Condition condition, string joinIndex) + { + if (_closed) + { + panicTrace("this.On call on already closed this. You should create new Query"); + } + if (_root == null) + { + throw new ReindexerNetException("Can't join on root query"); + } + _ser.PutVarCUInt((int)Bindings.Query.JoinOn); + _ser.PutVarCUInt((int)_nextOp); + _ser.PutVarCUInt((int)condition); + _ser.PutVString(index); + _ser.PutVString(joinIndex); + _nextOp = Bindings.Op.And; + return this; + } + + /// + public IQueryBuilder Select(params string[] fields) + { + foreach (string field in fields) + { + _ser.PutVarCUInt((int)Bindings.Query.SelectFilter); + _ser.PutVString(field); + } + return this; + } + + /// + public IQueryBuilder Functions(params string[] fields) + { + foreach (string field in fields) + { + _ser.PutVarCUInt((int)Bindings.Query.SelectFunction); + _ser.PutVString(field); + } + return this; + } + + /// + public IQueryBuilder EqualPosition(params string[] fields) + { + _ser.PutVarCUInt((int)Bindings.Query.EqualPosition); + if (_opennedBrackets.Count == 0) + { + _ser.PutVarCUInt(0); + } + else + { + _ser.PutVarCUInt(_opennedBrackets[_opennedBrackets.Count - 1] + 1); + } + _ser.PutVarCUInt(fields.Length); + foreach (string field in fields) + { + _ser.PutVString(field); + } + return this; + } + + /// + public ReadOnlySpan CloseQuery() + { + //var ns = db.GetNS(q.Namespace); + //if (ns != null) + //{ + // q.NsArray.Add(new NsArrayEntry { Ns = ns, CjsonState = ns.CjsonState.Copy() }); + //} + //else + //{ + // return (null, new ReindexerNetException("Namespace retrieval failed")); + //} + + //foreach (var sq in _mergedQueries) + //{ + // ns = db.GetNS(sq.Namespace); + // if (ns != null) + // { + // q.NsArray.Add(new NsArrayEntry { Ns = ns, CjsonState = ns.CjsonState.Copy() }); + // } + // else + // { + // return (null, new Exception("Namespace retrieval failed")); + // } + //} + + //foreach (var sq in _joinQueries) + //{ + // ns = db.GetNS(sq.Namespace); + // if (ns != null) + // { + // q.NsArray.Add(new NsArrayEntry { Ns = ns, CjsonState = ns.CjsonState.Copy() }); + // } + // else + // { + // return (null, new Exception("Namespace retrieval failed")); + // } + //} + + //foreach (var mq in q.MergedQueries) + //{ + // foreach (var sq in mq.JoinQueries) + // { + // ns = db.GetNS(sq.Namespace); + // if (ns != null) + // { + // q.NsArray.Add(new NsArrayEntry { Ns = ns, CjsonState = ns.CjsonState.Copy() }); + // } + // else + // { + // return (null, new Exception("Namespace retrieval failed")); + // } + // } + //} + + _ser.PutVarCUInt((int)Bindings.Query.End); + foreach (var sq in _joinQueries) + { + _ser.PutVarCUInt((int)sq._joinType); + _ser.Append(sq._ser); + _ser.PutVarCUInt((int)Bindings.Query.End); + } + + foreach (var mq in _mergedQueries) + { + _ser.PutVarCUInt((int)Bindings.JoinType.Merge); + _ser.Append(mq._ser); + _ser.PutVarCUInt((int)Bindings.Query.End); + foreach (var sq in mq._joinQueries) + { + _ser.PutVarCUInt((int)sq._joinType); + _ser.Append(sq._ser); + _ser.PutVarCUInt((int)Bindings.Query.End); + } + } + + //foreach (var nsEntry in q.NsArray) + //{ + // q.PtVersions.Add(nsEntry.LocalCjsonState.Version ^ nsEntry.LocalCjsonState.StateToken); + //} + + //if (asJson) + //{ + // // json iterator not support fetch queries + // FetchCount = -1; + //} + + _closed = true; + + return _ser.CurrentBuffer; + } + + /// + public void Dispose() + { + _ser.Dispose(); + } +} +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member \ No newline at end of file diff --git a/src/ReindexerNet.Core/IAsyncReindexerClient.cs b/src/ReindexerNet.Core/IAsyncReindexerClient.cs index 5283dad..7ff2a66 100644 --- a/src/ReindexerNet.Core/IAsyncReindexerClient.cs +++ b/src/ReindexerNet.Core/IAsyncReindexerClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -169,6 +170,24 @@ Task> EnumNamespacesAsync(string name = null, bool onlyNa /// Task DeleteAsync(string nsName, IEnumerable items, string[] precepts = null, CancellationToken cancellationToken = default); /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + /// + /// + Task> ExecuteAsync(string @namespace, Action query, CancellationToken cancellationToken = default); + /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + /// + /// + Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default); + /// /// Executes an sql query. /// /// Item type to return @@ -177,6 +196,14 @@ Task> EnumNamespacesAsync(string name = null, bool onlyNa /// Task> ExecuteSqlAsync(string sql, CancellationToken cancellationToken = default); /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + /// + Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default); + /// /// Executes an sql query. /// /// Sql query to perform. diff --git a/src/ReindexerNet.Core/IFilterQueryBuilder.cs b/src/ReindexerNet.Core/IFilterQueryBuilder.cs new file mode 100644 index 0000000..6596ae7 --- /dev/null +++ b/src/ReindexerNet.Core/IFilterQueryBuilder.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReindexerNet; + +/// +/// Represents filter query operation builder +/// +public interface IFilterQueryBuilder +{ + /// + /// Add where condition to DB query + /// + /// + /// + /// + /// + IQueryBuilder Where(string index, Condition condition, object keys); + /// + /// Add comparing two fields where condition to DB query + /// + /// + /// + /// + /// + IQueryBuilder WhereBetweenFields(string firstField, Condition condition, string secondField); + /// + /// Add where condition to DB query with bool args + /// + /// + /// + /// + /// + IQueryBuilder WhereBool(string index, Condition condition, params bool[] keys); + /// + /// Add where condition to DB query with interface args for composite indexes + /// + /// + /// + /// + /// + IQueryBuilder WhereComposite(string index, Condition condition, params object[] keys); + /// + /// Add where condition to DB query with float args + /// + /// + /// + /// + /// + IQueryBuilder WhereDouble(string index, Condition condition, params double[] keys); + /// + /// Add where condition to DB query with int args + /// + /// + /// + /// + /// + IQueryBuilder WhereInt(string index, Condition condition, params int[] keys); + /// + /// Add where condition to DB query with int args + /// + /// Same as + /// + /// + /// + /// + IQueryBuilder WhereInt32(string index, Condition condition, params int[] keys); + /// + /// Add where condition to DB query with int64(long) args + /// + /// + /// + /// + /// + IQueryBuilder WhereInt64(string index, Condition condition, params long[] keys); + /// + /// Add where condition to DB query with string args + /// + /// + /// + /// + /// + IQueryBuilder WhereString(string index, Condition condition, params string[] keys); + /// + /// Add where condition to DB query with guid args + /// + /// + /// + /// + /// + IQueryBuilder WhereUuid(string index, Condition condition, params string[] keys); +} diff --git a/src/ReindexerNet.Core/IQueryBuilder.cs b/src/ReindexerNet.Core/IQueryBuilder.cs new file mode 100644 index 0000000..37ab728 --- /dev/null +++ b/src/ReindexerNet.Core/IQueryBuilder.cs @@ -0,0 +1,263 @@ +using System; + +namespace ReindexerNet; + +/// +/// Represents generic query builder. +/// +public interface IQueryBuilder: IFilterQueryBuilder, IDisposable +{ + /// + /// sets the number of items that will be fetched by one operation + /// + int FetchCount { get; set; } + /// + /// + /// + string TotalName { get; set; } + /// + /// Adds Avarage Aggregate + /// + /// + /// + IQueryBuilder AggregateAvg(string field); + /// + /// + /// + /// + /// fields should not be empty. + /// + IQueryBuilder AggregateFacet(Action aggFacetQuery, params string[] fields); + /// + /// Adds Max Aggregate + /// + /// + /// + IQueryBuilder AggregateMax(string field); + /// + /// Adds Min Aggregate + /// + /// + /// + IQueryBuilder AggregateMin(string field); + /// + /// Adds Sum Aggregate + /// + /// + /// + IQueryBuilder AggregateSum(string field); + /// + /// next condition will added with AND. + /// This is the default operation for WHERE statement. Do not have to be called explicitly in user's code. Used in DSL convertion + /// + /// + IQueryBuilder And(); + /// + /// Request cached total items calculation + /// + /// + /// + IQueryBuilder CachedTotal(params string[] totalNames); + + /// + /// Return only items with uniq value of field + /// + /// + /// + IQueryBuilder Distinct(string distinctIndex); + /// + /// Add DWithin condition to DB query + /// + /// + /// + /// + /// + IQueryBuilder DWithin(string index, (double start, double end) point, double distance); + /// + /// Adds equal position fields to arrays + /// + /// + /// + IQueryBuilder EqualPosition(params string[] fields); + /// + /// Request explain for query + /// + /// + IQueryBuilder Explain(); + /// + /// add optional select functions (e.g highlight or snippet ) to fields of result's objects + /// + /// + /// + IQueryBuilder Functions(params string[] fields); + /// + /// joins 2 queries + /// Items from the 1-st query are filtered by and expanded with the data from the 2-nd query + /// + /// + /// + /// parameter serves as unique identifier for the join between `q` and `q2` + /// + IQueryBuilder InnerJoin(string otherNamespace, Action otherQuery, string field); + /// + /// This method is an alias for + /// + /// + /// + /// + /// + IQueryBuilder Join(string otherNamespace, Action otherQuery, string field); + /// + /// joins 2 queries + /// Items from the 1-st query are expanded with the data from the 2-nd query + /// + /// + /// + /// parameter serves as unique identifier for the join between `q` and `q2` + /// + IQueryBuilder LeftJoin(string otherNamespace, Action otherQuery, string field); + /// + /// Set limit (count) of returned items + /// + /// + /// + IQueryBuilder Limit(int limitItems); + /// + /// Add where condition to DB query with string args + /// + /// + /// + /// + IQueryBuilder Match(string index, params string[] keys); + /// + /// Merge 2 queries + /// + /// + /// + /// + IQueryBuilder Merge(string otherNamespace, Action otherQuery); + /// + /// next condition will added with NOT AND. + /// Implements short-circuiting: + /// if the previous condition is failed the next will not be evaluated + /// + /// + IQueryBuilder Not(); + /// + /// Set start offset of returned items + /// + /// + /// + IQueryBuilder Offset(int startOffset); + /// + /// Specifies join condition + /// + /// specifies which field from `q` namespace should be used during join + /// specifies how `q` will be joined with the latest join query issued on `q` (e.g. `EQ`/`GT`/`SET`/...) + /// specifies which field from namespace for the latest join query issued on `q` should be used during join + /// + IQueryBuilder On(string index, Condition condition, string joinIndex); + /// + /// next condition will added with OR. + /// Implements short-circuiting: + /// if the previous condition is successful the next will not be evaluated, but except Join conditions + /// + /// + IQueryBuilder Or(); + /// + /// Request total items calculation + /// + /// + /// + IQueryBuilder ReqTotal(params string[] totalNames); + /// + /// add filter to fields of result's objects + /// + /// + /// + IQueryBuilder Select(params string[] fields); + /// + /// Apply sort order to returned from query items. + /// If values argument specified, then items equal to values, if found will be placed in the top positions. + /// For composite indexes values must be []interface{}, with value of each subindex. + /// Forced sort is support for the first sorting field only + /// + /// + /// + /// + /// + IQueryBuilder Sort(string sortIndex, bool desc, params object[] values); + /// + /// wrapper for geometry sorting by shortes distance between 2 geometry fields (ST_Distance) + /// + /// + /// + /// + /// + IQueryBuilder SortStFieldDistance(string field1, string field2, bool desc); + /// + /// wrapper for geometry sorting by shortes distance between geometry field and point (ST_Distance) + /// + /// + /// + /// + /// + IQueryBuilder SortStPointDistance(string field, (double X, double Y) p, bool desc); + /// + /// Set query strict mode + /// + /// + /// + IQueryBuilder Strict(QueryStrictMode mode); + /// + /// Creates sub filter query. Equals to Brackets () in an SQL query in the whre condition. + /// + /// + /// + IQueryBuilder Where(Action filterQuery); + /// + /// Output fulltext rank. + /// Allowed only with fulltext query + /// + /// + IQueryBuilder WithRank(); +} + +/// +/// Aggregate Facet Request +/// +public interface IAggregateFacetRequest +{ + /// + /// + /// + /// + /// + IAggregateFacetRequest Limit(int limit); + /// + /// + /// + /// + /// + IAggregateFacetRequest Offset(int offset); + /// + /// Use field 'count' to sort by facet's count value. + /// + /// + /// + /// + IAggregateFacetRequest Sort(string field, bool desc); +} + +/// +/// Represents serialization methods of a query builder. +/// +public interface ISerializableQueryBuilder +{ + /// + /// Closes query and returns query as a byte array of the serialized query. Should not call explictly inside a query call like Execute method of the client. + /// And Should not do any query operations after this call. Only proper call is Dispose method of the query after this call. + /// + /// + ReadOnlySpan CloseQuery(); +} \ No newline at end of file diff --git a/src/ReindexerNet.Core/IReindexerClient.cs b/src/ReindexerNet.Core/IReindexerClient.cs index 3d32c46..3c14a84 100644 --- a/src/ReindexerNet.Core/IReindexerClient.cs +++ b/src/ReindexerNet.Core/IReindexerClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; namespace ReindexerNet; @@ -144,6 +145,22 @@ IEnumerable EnumNamespaces(string name = null, bool onlyNames = false /// int Delete(string nsName, IEnumerable items, string[] precepts = null); /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + /// + QueryItemsOf Execute(string @namespace, Action query); + /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + /// + QueryItemsOf Execute(byte[] query, SerializerType queryEncoding); + /// /// Executes an sql query. /// /// Item type to return @@ -151,6 +168,13 @@ IEnumerable EnumNamespaces(string name = null, bool onlyNames = false /// QueryItemsOf ExecuteSql(string sql); /// + /// Executes a reindexer nosql query + /// + /// + /// + /// + QueryItemsOf Execute(byte[] query, SerializerType queryEncoding); + /// /// Executes an sql query. /// /// Sql query to perform. @@ -180,5 +204,5 @@ IEnumerable EnumNamespaces(string name = null, bool onlyNames = false /// /// /// - IEnumerable EnumMeta(string nsName); + IEnumerable EnumMeta(string nsName); } diff --git a/src/ReindexerNet.Core/IUpdateQueryBuilder.cs b/src/ReindexerNet.Core/IUpdateQueryBuilder.cs new file mode 100644 index 0000000..78fbb39 --- /dev/null +++ b/src/ReindexerNet.Core/IUpdateQueryBuilder.cs @@ -0,0 +1,35 @@ +namespace ReindexerNet; + +/// +/// Represents update query builder. +/// +public interface IUpdateQueryBuilder +{ + /// + /// Drop removes field from item within Update statement + /// + /// + /// + IQueryBuilder Drop(string field); + /// + /// Set adds update field request for update query + /// + /// + /// + /// + IQueryBuilder Set(string field, object values); + /// + /// SetExpression updates indexed field by arithmetical expression + /// + /// + /// + /// + IQueryBuilder SetExpression(string field, string value); + /// + /// SetObject adds update of object field request for update query + /// + /// + /// + /// + IQueryBuilder SetObject(string field, object values); +} diff --git a/src/ReindexerNet.Core/Internal/Bindings.cs b/src/ReindexerNet.Core/Internal/Bindings.cs new file mode 100644 index 0000000..fcdfdda --- /dev/null +++ b/src/ReindexerNet.Core/Internal/Bindings.cs @@ -0,0 +1,224 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ReindexerNet.EmbeddedTest")] + +namespace ReindexerNet.Internal; + +internal static class Bindings +{ + public const int CInt32Max = int.MaxValue; + public const string ReindexerVersion = "v3.20.0"; + + + internal enum LogLevel + { + ERROR = 1, + WARNING = 2, + INFO = 3, + TRACE = 4 + } + + internal enum Agg + { + Sum = 0, + Avg = 1, + Facet = 2, + Min = 3, + Max = 4, + Distinct = 5 + } + + internal enum CollationType + { + None = 0, + ASCII = 1, + UTF8 = 2, + Numeric = 3, + Custom = 4 + } + + internal enum Op + { + Or = 1, + And = 2, + Not = 3 + } + + internal enum Value + { + Int64 = 0, + Double = 1, + String = 2, + Bool = 3, + Null = 4, + Int = 8, + Undefined = 9, + Composite = 10, + Tuple = 11, + Uuid = 12 + } + + internal enum Query + { + Condition = 0, + Distinct = 1, + SortIndex = 2, + JoinOn = 3, + Limit = 4, + Offset = 5, + ReqTotal = 6, + DebugLevel = 7, + Aggregation = 8, + SelectFilter = 9, + SelectFunction = 10, + End = 11, + Explain = 12, + EqualPosition = 13, + UpdateField = 14, + AggregationLimit = 15, + AggregationOffset = 16, + AggregationSort = 17, + OpenBracket = 18, + CloseBracket = 19, + JoinCondition = 20, + DropField = 21, + UpdateObject = 22, + WithRank = 23, + StrictMode = 24, + UpdateFieldV2 = 25, + BetweenFieldsCondition = 26, + AlwaysFalseCondition = 27 + } + + internal enum JoinType + { + LeftJoin = 0, + InnerJoin = 1, + OrInnerJoin = 2, + Merge = 3 + } + + internal enum CacheMode + { + On = 0, + Aggressive = 1, + Off = 2 + } + + internal enum FormatType + { + Json = 0, + CJson = 1 + } + + internal enum OperationMode + { + Update = 0, + Insert = 1, + Upsert = 2, + Delete = 3 + } + + internal enum TotalMode + { + NoCalc = 0, + CachedTotal = 1, + AccurateTotal = 2 + } + + internal enum QueryResultType + { + End = 0, + Aggregation = 1, + Explain = 2 + } + + internal enum StrictModeType + { + NotSet = 0, + None = 1, + Names = 2, + Indexes = 3 + } + + internal enum ResultsFormat + { + Pure = 0x0, + Ptrs = 0x1, + CJson = 0x2, + Json = 0x3 + } + + internal enum ResultsOptions + { + WithPayloadTypes = 0x10, + WithItemID = 0x20, + WithPercents = 0x40, + WithNsID = 0x80, + WithJoined = 0x100, + SupportIdleTimeout = 0x2000 + } + + internal enum IndexOptions + { + OptPK = 1 << 7, + OptArray = 1 << 6, + OptDense = 1 << 5, + OptAppendable = 1 << 4, + OptSparse = 1 << 3 + } + + internal enum StorageOptions + { + Enabled = 1, + DropOnFileFormatError = 1 << 1, + CreateIfMissing = 1 << 2 + } + + internal enum ConnectOptions + { + OpenNamespaces = 1, + AllowNamespaceErrors = 1 << 1, + Autorepair = 1 << 2, + WarnVersion = 1 << 4 + } + + internal enum ErrorCode + { + OK = 0, + ParseSQL = 1, + QueryExec = 2, + Params = 3, + Logic = 4, + ParseJson = 5, + ParseDSL = 6, + Conflict = 7, + ParseBin = 8, + Forbidden = 9, + WasRelock = 10, + NotValid = 11, + Network = 12, + NotFound = 13, + StateInvalidated = 14, + Timeout = 19, + Canceled = 20, + TagsMismatch = 21, + ReplParams = 22, + NamespaceInvalidated = 23, + ParseMsgPack = 24, + ParseProtobuf = 25, + UpdatesLost = 26, + WrongReplicationData = 27, + UpdateReplication = 28, + ClusterConsensus = 29, + Terminated = 30, + TxDoesNotExist = 31, + AlreadyConnected = 32, + TxInvalidLeader = 33, + AlreadyProxied = 34, + StrictMode = 35, + QrUIDMismatch = 36, + System = 37, + Assert = 38 + } +} \ No newline at end of file diff --git a/src/ReindexerNet.Core/Internal/CJsonReader.cs b/src/ReindexerNet.Core/Internal/CJsonReader.cs new file mode 100644 index 0000000..6e5c9d2 --- /dev/null +++ b/src/ReindexerNet.Core/Internal/CJsonReader.cs @@ -0,0 +1,386 @@ +#pragma warning disable S1481 // Unused local variables should be removed +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ReindexerNet.Internal; + +ref struct CJsonReader +{ + public const int ResultsFormatMask = 0xF; + public const int ResultsPure = 0x0; + public const int ResultsPtrs = 0x1; + public const int ResultsCJson = 0x2; + public const int ResultsJson = 0x3; + + public const int ResultsWithPayloadTypes = 0x10; + public const int ResultsWithItemID = 0x20; + public const int ResultsWithPercents = 0x40; + public const int ResultsWithNsID = 0x80; + public const int ResultsWithJoined = 0x100; + + + private readonly ReadOnlySpan _buffer; + private int _flags { get; set; } + private int _pos { get; set; } + + public CJsonReader(ReadOnlySpan buffer) + { + _buffer = buffer; + _flags = 0; + _pos = 0; + } + + public RawResultItemParams ReadRawItemParams() + { + var v = new RawResultItemParams(); + if ((_flags & ResultsWithItemID) != 0) + { + v.id = (int)GetVarUInt(); + v.version = (int)GetVarUInt(); + } + + if ((_flags & ResultsWithNsID) != 0) + { + v.nsid = (int)GetVarUInt(); + } + + if ((_flags & ResultsWithPercents) != 0) + { + v.proc = (int)GetVarUInt(); + } + + switch (_flags & ResultsFormatMask) + { + case ResultsPure: + case ResultsPtrs: + v.cptr = (UIntPtr)GetUInt64(); + break; + case ResultsJson: + case ResultsCJson: + v.data = GetBytes(); + break; + } + return v; + } + + public RawResultQueryParams ReadRawQueryParams(/*Action updatePayloadType = null*/) + { + var v = new RawResultQueryParams(); + v.aggResults = []; + v.flags = (int)GetVarUInt(); + v.totalcount = (int)GetVarUInt(); + v.qcount = (int)GetVarUInt(); + v.count = (int)GetVarUInt(); + + if ((v.flags & ResultsWithPayloadTypes) != 0) + { + var ptCount = (int)GetVarUInt(); + for (var i = 0; i < ptCount; i++) + { + var nsid = (int)GetVarUInt(); + var nsName = GetVString(); + +#pragma warning disable S125 // Sections of code should not be commented out + //if (updatePayloadType == null) + //{ + // throw new Exception("Internal error: Got payload types from raw query params, but there are no updatePayloadType"); + //} + //updatePayloadType(nsid); +#pragma warning restore S125 // Sections of code should not be commented out + ReadPayloadType(); + } + } + ReadExtraResults(ref v); + _flags = v.flags; + + return v; + } + + public void ReadExtraResults(ref RawResultQueryParams v) + { + while (true) + { + var tag = (QueryResultTag)GetVarUInt(); + if (tag == QueryResultTag.End) + { + break; + } + + var data = GetBytes(); + switch (tag) + { + case QueryResultTag.Explain: + v.explainResults = data; + break; + case QueryResultTag.Aggregation: + v.aggResults.Add(data.ToArray()); + break; + } + } + } + + public void ReadPayloadType() + { + var stateToken = GetVarUInt(); + var version = GetVarUInt(); + +#pragma warning disable S125 // Sections of code should not be commented out + // skip := state.Version >= version && state.StateToken == stateToken + + //if !skip { + // state.StateData = &StateData{Version: version, StateToken: stateToken} + //} + + + //state.tagsMatcher.Read(s, skip) + var tagsCount = (int)GetVarUInt(); + //if !skip { + // tm.Tags = make([]string, tagsCount, tagsCount) + // tm.Names = make(map[string]int) + + // for i := 0; i < tagsCount; i++ { + // tm.Tags[i] = ser.GetVString() + // tm.Names[tm.Tags[i]] = i + // } + //} else { + for (var i = 0; i < tagsCount; i++) + { + var tag = GetVString(); + } + //} + + + //state.payloadType.Read(s, skip) + + var pStringHdrOffset = (UIntPtr)GetVarUInt(); + var fieldsCount = (int)GetVarUInt(); + //fields := make([]payloadFieldType, fieldsCount, fieldsCount) + var fields = new PayloadFieldType[fieldsCount]; + for (var i = 0; i < fieldsCount; i++) + { + var payloadFieldType = new PayloadFieldType(); + payloadFieldType.Type = (int)GetVarUInt(); + payloadFieldType.Name = GetVString(); + payloadFieldType.Offset = (UIntPtr)GetVarUInt(); + payloadFieldType.Size = (UIntPtr)GetVarUInt(); + payloadFieldType.IsArray = GetVarUInt() != 0; + + fields[i] = payloadFieldType; + var jsonPathCnt = GetVarUInt(); + for (; jsonPathCnt != 0; jsonPathCnt--) + { + GetVString(); + } + } + //if !skip { + // pt.Fields = fields + //} +#pragma warning restore S125 // Sections of code should not be commented out + } + + private +#if !NETSTANDARD2_0 && !NET472 + readonly +#else + static +#endif + void checkbound(int pos, int need, int len) + { + if (pos + need > len) + { + throw new InternalBufferOverflowException($"Binary buffer underflow. Need more {need} bytes, pos={pos},len={len}"); + } + } + + private +#if !NETSTANDARD2_0 && !NET472 + readonly +#else + static +#endif + int scan_varint(int len, ReadOnlySpan data) + { + int i; + if (len > 10) len = 10; + for (i = 0; i < len; i++) + if ((data[i] & 0x80) == 0) break; + if (i == len) return 0; + return i + 1; + } + + private +#if !NETSTANDARD2_0 && !NET472 + readonly +#else + static +#endif + uint parse_uint32(uint len, ReadOnlySpan data) + { + uint rv = data[0] & (uint)sbyte.MaxValue; + if (len > 1U) + { + rv |= (uint)((data[1] & sbyte.MaxValue) << 7); + if (len > 2U) + { + rv |= (uint)((data[2] & sbyte.MaxValue) << 14); + if (len > 3U) + { + rv |= (uint)((data[3] & sbyte.MaxValue) << 21); + if (len > 4U) + rv |= (uint)data[4] << 28; + } + } + } + return rv; + } + + private +#if !NETSTANDARD2_0 && !NET472 + readonly +#else + static +#endif + ulong parse_uint64(uint len, ReadOnlySpan data) + { + ulong rv; + if (len < 5U) + { + rv = parse_uint32(len, data); + } + else + { + ulong shifted = (ulong)((long)(data[0] & sbyte.MaxValue) | (long)(data[1] & sbyte.MaxValue) << 7 | (long)(data[2] & sbyte.MaxValue) << 14 | (long)(data[3] & sbyte.MaxValue) << 21); + uint shift = 28; + for (uint index = 4; index < len; ++index) + { + shifted |= (ulong)(data[(int)index] & sbyte.MaxValue) << (int)shift; + shift += 7U; + } + rv = shifted; + } + return rv; + } + + public ulong GetVarUInt() + { + var l = scan_varint(_buffer.Length - _pos, _buffer.Slice(_pos)); + if (l == 0) + { + throw new InvalidDataException($"Binary buffer broken - scan_varint failed: pos={_pos},len={_buffer.Length}"); + } + checkbound(_pos, l, _buffer.Length); + _pos += l; + return parse_uint64((uint)l, _buffer.Slice(_pos - l)); + } + + private long ReadIntBits(int size) + { + long v = 0; + for (var i = size - 1; i >= 0; i--) + { + v = ((long)(_buffer[i + _pos]) & 0xFF) | (v << 8); + } + _pos += size; + return v; + } + + private ulong ReadUIntBits(int size) + { + ulong v = 0; + for (var i = size - 1; i >= 0; i--) + { + v = ((ulong)(_buffer[i + _pos]) & 0xFF) | (v << 8); + } + _pos += size; + return v; + } + + private uint GetUInt32() + { + checkbound(_pos, sizeof(uint), _buffer.Length); + return (uint)ReadIntBits(sizeof(uint)); + } + + private ulong GetUInt64() + { + checkbound(_pos, sizeof(ulong), _buffer.Length); + return ReadUIntBits(sizeof(ulong)); + } + + private ReadOnlySpan GetBytes() + { + var l = (int)GetUInt32(); + if (_pos + l > _buffer.Length) + { + throw new InternalBufferOverflowException($"Internal error: serializer need {l} bytes, but only {_buffer.Length - _pos} available"); + } + + var v = _buffer.Slice(_pos, l); + _pos += l; + return v; + } + + private string GetVString() + { + var l = (int)GetVarUInt(); + if (_pos + l > _buffer.Length) + { + throw new InternalBufferOverflowException($"Internal error: serializer need {l} bytes, but only {_buffer.Length - _pos} available"); + } + + var v = Encoding.UTF8.GetString(_buffer.Slice(_pos, l) +#if NETSTANDARD2_0 || NET472 + .ToArray() +#endif + ); + _pos += l; + return v; + } +} + +internal ref struct RawResultItemParams +{ + public int id; + public int version; + public int nsid; + public int proc; + public UIntPtr cptr; + public ReadOnlySpan data; +} + +internal class RawResultsExtraParam +{ + public int Tag; + public string Name; + public byte[] Data; +} + +internal ref struct RawResultQueryParams +{ + public int flags; + public int totalcount; + public int qcount; + public int count; + public List aggResults; + public ReadOnlySpan explainResults; +} + +internal class PayloadFieldType +{ + internal int Type; + internal string Name; + internal UIntPtr Offset; + internal UIntPtr Size; + internal bool IsArray; +} + +enum QueryResultTag +{ + End = 0, + Aggregation = 1, + Explain = 2 +} + +#pragma warning restore S1481 // Unused local variables should be removed \ No newline at end of file diff --git a/src/ReindexerNet.Core/Internal/CJsonWriter.cs b/src/ReindexerNet.Core/Internal/CJsonWriter.cs new file mode 100644 index 0000000..3328a51 --- /dev/null +++ b/src/ReindexerNet.Core/Internal/CJsonWriter.cs @@ -0,0 +1,230 @@ +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static ReindexerNet.Internal.Bindings; + +[assembly: InternalsVisibleTo("ReindexerNet.Embedded")] + +namespace ReindexerNet.Internal; + +internal sealed class CJsonWriter : IDisposable +{ + private readonly ArrayPool _pool; + private byte[] _buffer; + private int _pos; + public CJsonWriter() + { + _pool = ArrayPool.Shared; + _buffer = _pool.Rent(100); + } + + public ReadOnlySpan CurrentBuffer => _buffer.AsSpan().Slice(0, _pos); + + private void EnsureRemainingSize(int length) + { + if (_pos + length > _buffer.Length) + { + var oldBufferRef = _buffer; + _buffer = _pool.Rent(_buffer.Length * 2); + _pool.Return(oldBufferRef); + } + } + + public void PutUInt32(uint v) + { + EnsureRemainingSize(sizeof(uint)); + MemoryMarshal.Write(_buffer.AsSpan(_pos), ref v); + _pos += sizeof(uint); + } + + public void PutUInt64(ulong v) + { + EnsureRemainingSize(sizeof(ulong)); + MemoryMarshal.Write(_buffer.AsSpan(_pos), ref v); + _pos += sizeof(ulong); + } + + public void PutDouble(double v) + { + EnsureRemainingSize(sizeof(double)); + MemoryMarshal.Write(_buffer.AsSpan(_pos), ref v); + _pos += sizeof(double); + } + + public void PutVarInt(long v) + { + EnsureRemainingSize(10); + var len = sint64_pack(v, _buffer.AsSpan().Slice(_pos)); + _pos += (int)len; + } + + public void PutVarUInt(ulong v) + { + EnsureRemainingSize(10); + var len = uint64_pack(v, _buffer.AsSpan().Slice(_pos)); + _pos += (int)len; + } + + public void PutVarCUInt(int v) + { + PutVarUInt((ulong)v); + } + + public void PutVString(string v) + { + if (v == null) + { + _buffer[_pos++] = 0; + return; + } + + var strArr = v.AsSpan(); + EnsureRemainingSize(10 + strArr.Length); + var currentPos = _buffer.AsSpan().Slice(_pos); + var lenSize = (int)uint32_pack((uint)strArr.Length, currentPos); + currentPos = currentPos.Slice(lenSize); + for (int i = 0; i < strArr.Length; i++) + { + currentPos[i] = (byte)strArr[i]; + } + _pos += lenSize + strArr.Length; + } + + public void PutUuid(Guid uuid) + { + byte[] bytes = uuid.ToByteArray(); + PutUInt64(BitConverter.ToUInt64(bytes, 0)); // First 8 bytes + PutUInt64(BitConverter.ToUInt64(bytes, 8)); // Next 8 bytes + } + + public void Append(CJsonWriter other) + { + #if NETSTANDARD2_0 || NET472 + var otherSpan = other._buffer.AsSpan().Slice(other._pos); + #else + var otherSpan = other._buffer.AsSpan()[..other._pos]; + #endif + EnsureRemainingSize(otherSpan.Length); + otherSpan.CopyTo(_buffer.AsSpan(_pos)); + _pos += otherSpan.Length; + } + + private static ulong zigzag64(long v) + { + if (v < 0) + return (ulong)(-v) * 2 - 1; + else + return (ulong)v * 2; + } + + private static uint sint64_pack(long value, Span @out) + { + return uint64_pack(zigzag64(value), @out); + } + + private static uint uint32_pack(uint value, Span @out) + { + uint rv = 0; + if (value >= 128U) + { + @out[(int)rv] = (byte)((int)value | 128); + ++rv; + value >>= 7; + if (value >= 128U) + { + @out[(int)rv] = (byte)((int)value | 128); + ++rv; + value >>= 7; + if (value >= 128U) + { + @out[(int)rv] = (byte)((int)value | 128); + ++rv; + value >>= 7; + if (value >= 128U) + { + @out[(int)rv] = (byte)((int)value | 128); + ++rv; + value >>= 7; + } + } + } + } + @out[(int)rv * 1] = (byte)value; + return rv + 1U; + } + + private static uint uint64_pack(ulong value, Span @out) + { + uint hi = (uint)(value >> 32); + uint lo = (uint)value; + uint rv; + if (hi == 0U) + { + rv = uint32_pack(lo, @out); + } + else + { + @out[0] = (byte)((int)lo | 128); + @out[1] = (byte)((int)(lo >> 7) | 128); + @out[2] = (byte)((int)(lo >> 14) | 128); + @out[3] = (byte)((int)(lo >> 21) | 128); + if (hi < 8U) + { + @out[4] = (byte)((int)hi << 4 | (int)(lo >> 28)); + rv = 5U; + } + else + { + @out[4] = (byte)(((int)hi & 7) << 4 | (int)(lo >> 28) | 128); + uint num4 = hi >> 3; + uint num5 = 5; + for (; num4 >= 128U; num4 >>= 7) + { + @out[(int)num5 * 1] = (byte)((int)num4 | 128); + ++num5; + } + @out[(int)num5 * 1] = (byte)num4; + rv = num5 + 1U; + } + } + return rv; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _pool.Return(_buffer); + } + + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. + + disposedValue = true; + } + } + + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~CJsonBuffer() + // { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + // TODO: uncomment the following line if the finalizer is overridden above. + // GC.SuppressFinalize(this); + } + #endregion + +} diff --git a/src/ReindexerNet.Core/Model/Condtion.cs b/src/ReindexerNet.Core/Model/Condtion.cs new file mode 100644 index 0000000..fea34f1 --- /dev/null +++ b/src/ReindexerNet.Core/Model/Condtion.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReindexerNet; + +/// +/// Represents logical conditions in a reindexer query. +/// +public enum Condition +{ + /// + /// Any of + /// + ANY = 0, + /// + /// Equals + /// + EQ = 1, + /// + /// Less Than + /// + LT = 2, + /// + /// Less or Equal + /// + LE = 3, + /// + /// Greater Than + /// + GT = 4, + /// + /// Greater Or Equal + /// + GE = 5, + /// + /// Between range + /// + RANGE = 6, + /// + /// Set + /// + SET = 7, + /// + /// AllSet + /// + ALLSET = 8, + /// + /// Empty + /// + EMPTY = 9, + /// + /// Like + /// + LIKE = 10, + + /// + /// Geometry subsection + /// + DWITHIN = 11 +} \ No newline at end of file diff --git a/src/ReindexerNet.Core/Model/Query.cs b/src/ReindexerNet.Core/Model/Query.cs index d2ba30b..580e19b 100644 --- a/src/ReindexerNet.Core/Model/Query.cs +++ b/src/ReindexerNet.Core/Model/Query.cs @@ -86,6 +86,13 @@ public class Query [JsonPropertyName("select_functions")] public List SelectFunctions { get; set; } + /// + /// Output fulltext rank in QueryResult. Allowed only with fulltext query Default : false + /// + [DataMember(Name = "select_with_rank", EmitDefaultValue = false)] + [JsonPropertyName("select_with_rank")] + public bool? SelectWithRank { get; set; } + /// /// Ask query calculate aggregation /// @@ -110,6 +117,12 @@ public class Query [JsonPropertyName("explain")] public bool? Explain { get; set; } + /// + /// Strict mode for query. Adds additional check for fields('names')/indexes('indexes') existence in sorting and filtering conditions. enum (none, names, indexes) Default : "names" + /// + [DataMember(Name = "strict_mode ", EmitDefaultValue = false)] + [JsonPropertyName("strict_mode ")] + public string? StrictMode { get; set; } /// /// Get the string presentation of the object diff --git a/src/ReindexerNet.Core/NetStandard20Fixes.cs b/src/ReindexerNet.Core/NetStandard20Fixes.cs new file mode 100644 index 0000000..7688a51 --- /dev/null +++ b/src/ReindexerNet.Core/NetStandard20Fixes.cs @@ -0,0 +1,299 @@ +#if NETSTANDARD2_0 || NET472 +#region record Fix +namespace System.Runtime.CompilerServices +{ + public class IsExternalInit { } +} +#endregion + +#region System.Range, System.Index Fix +// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Index.cs +// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Range.cs + +namespace System +{ + using System.Runtime.CompilerServices; + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// + internal readonly struct Index : IEquatable + { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + { + return ~_value; + } + else + { + return _value; + } + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + var offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return "^" + ((uint)Value).ToString(); + + return ((uint)Value).ToString(); + } + } + + /// Represent a range has start and end indexes. + /// + /// Range is used by the C# compiler to support the range syntax. + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; + /// int[] subArray1 = someArray[0..2]; // { 1, 2 } + /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } + /// + /// + internal readonly struct Range : IEquatable + { + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return Start.GetHashCode() * 31 + End.GetHashCode(); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start + ".." + End; + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start; + var startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + var endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } + } +} + +namespace System.Runtime.CompilerServices +{ + internal static class RuntimeHelpers + { + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T) != null || typeof(T[]) == array.GetType()) + { + // We know the type of the array to be exactly T[]. + + if (length == 0) + { + return Array.Empty(); + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + } +} +#endregion + +#region Dictionary deconstruct +namespace System.Collections.Generic +{ + public static class KeyValuePairExtension + { + public static void Deconstruct(this KeyValuePair source, out TKey key, out TValue value) + { + key = source.Key; + value = source.Value; + } + } +} +#endregion + +#endif \ No newline at end of file diff --git a/src/ReindexerNet.Core/ReindexerJsonSerializer.cs b/src/ReindexerNet.Core/ReindexerJsonSerializer.cs index 65b7312..aec3f83 100644 --- a/src/ReindexerNet.Core/ReindexerJsonSerializer.cs +++ b/src/ReindexerNet.Core/ReindexerJsonSerializer.cs @@ -1,5 +1,8 @@ using System; +using System.Buffers.Text; +using System.Buffers; using System.Text.Json; +using System.Text.Json.Serialization; namespace ReindexerNet; @@ -10,7 +13,8 @@ public class ReindexerJsonSerializer : IReindexerSerializer { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + Converters = { new DoubleConverter() } }; /// @@ -28,3 +32,94 @@ public ReadOnlySpan Serialize(T item) return JsonSerializer.SerializeToUtf8Bytes(item, _jsonSerializerOptions); } } + +class DoubleConverter : JsonConverter +{ + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => reader.GetDouble(); +#if NET6_0_OR_GREATER + static readonly StandardFormat f = StandardFormat.Parse("F"); + static readonly StandardFormat g = StandardFormat.Parse("G"); + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + Span buffer = stackalloc byte[30]; + var format = value % 1 == 0 ? f : g; + Utf8Formatter.TryFormat(value, buffer, out var written, format); + writer.WriteRawValue(buffer[..written]); + } +#else + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + //https://stackoverflow.com/questions/66309150/how-do-i-write-a-float-like-82-0-with-the-0-intact-using-utf8jsonwriter + // 2^49 is the largest power of 2 with fewer than 15 decimal digits. + // From experimentation casting to decimal does not lose precision for these values. + const double MaxPreciselyRepresentedIntValue = (1L << 49); + + bool written = false; + // For performance check to see that the incoming double is an integer + if ((value % 1) == 0) + { + if (value < MaxPreciselyRepresentedIntValue && value > -MaxPreciselyRepresentedIntValue) + { + writer.WriteNumberValue(0.0m + (decimal)value); + written = true; + } + else + { + // Directly casting these larger values from double to decimal seems to result in precision loss, as noted in https://stackoverflow.com/q/7453900/3744182 + // And also: https://learn.microsoft.com/en-us/dotnet/api/system.convert.todecimal?redirectedfrom=MSDN&view=net-5.0#System_Convert_ToDecimal_System_Double_ + // > The Decimal value returned by Convert.ToDecimal(Double) contains a maximum of 15 significant digits. + // So if we want the full G17 precision we have to format and parse ourselves. + // + // Utf8Formatter and Utf8Parser should give the best performance for this, but, according to MSFT, + // on frameworks earlier than .NET Core 3.0 Utf8Formatter does not produce roundtrippable strings. For details see + // https://github.com/dotnet/runtime/blob/eb03e0f7bc396736c7ac59cf8f135d7c632860dd/src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs#L103 + // You may want format to string and parse in earlier frameworks -- or just use JsonDocument on these earlier versions. + Span utf8bytes = stackalloc byte[32]; + if (Utf8Formatter.TryFormat(value, utf8bytes.Slice(0, utf8bytes.Length - 2), out var bytesWritten) + && IsInteger(utf8bytes, bytesWritten)) + { + utf8bytes[bytesWritten++] = (byte)'.'; + utf8bytes[bytesWritten++] = (byte)'0'; + if (Utf8Parser.TryParse(utf8bytes.Slice(0, bytesWritten), out decimal d, out var _)) + { + writer.WriteNumberValue(d); + written = true; + } + } + } + } + if (!written) + { + #if NETSTANDARD2_0 || NET472 + static unsafe bool IsFinite(double d) + { + long bits = BitConverter.DoubleToInt64Bits(d); + return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000; + } + var isFinite = IsFinite(value); + #else + var isFinite = double.IsFinite(value); + #endif + if (isFinite) + writer.WriteNumberValue(value); + else + // Utf8JsonWriter does not take into account JsonSerializerOptions.NumberHandling so we have to make a recursive call to serialize + JsonSerializer.Serialize(writer, value, new JsonSerializerOptions { NumberHandling = options.NumberHandling }); + } + + static bool IsInteger(Span utf8bytes, int bytesWritten) + { + if (bytesWritten <= 0) + return false; + var start = utf8bytes[0] == '-' ? 1 : 0; + for (var i = start; i < bytesWritten; i++) + if (!(utf8bytes[i] >= '0' && utf8bytes[i] <= '9')) + return false; + return start < bytesWritten; + } + } +#endif +} + diff --git a/src/ReindexerNet.Core/ReindexerNet.Core.csproj b/src/ReindexerNet.Core/ReindexerNet.Core.csproj index 3660c34..6293595 100644 --- a/src/ReindexerNet.Core/ReindexerNet.Core.csproj +++ b/src/ReindexerNet.Core/ReindexerNet.Core.csproj @@ -1,14 +1,15 @@  + true ReindexerNet ReindexerNet.Core ReindexerNet.Core ReindexerNet.Core includes rest api models for Reindexer and includes base interfaces for ReindexerNet. reindexer embedded document-db in-memory - 0.3.10 - 0.3.10 - 0.3.10 + 0.4.0 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Core/SerializableQueryBuilder.cs b/src/ReindexerNet.Core/SerializableQueryBuilder.cs new file mode 100644 index 0000000..5e6bbb0 --- /dev/null +++ b/src/ReindexerNet.Core/SerializableQueryBuilder.cs @@ -0,0 +1,490 @@ +using ReindexerNet.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ReindexerNet; + +/// +/// Builds object from given query, and serialize with target serializer. +/// +public sealed class SerializableQueryBuilder : IQueryBuilder, ISerializableQueryBuilder +{ + private readonly IReindexerSerializer _serializer; + private readonly string _namespace; + private readonly Query _query = new(); + private Bindings.Op? _nextOp = null; + + /// + /// Builds object from given query, and serialize with target serializer. + /// + /// + /// + public SerializableQueryBuilder(IReindexerSerializer serializer, string @namespace) + { + _serializer = serializer; + _namespace = @namespace; + _query.Namespace = @namespace; + } + + /// + public int FetchCount { get; set; } + /// + public string TotalName { get; set; } + + private static string GetAggregateType(Bindings.Agg agg) => agg switch + { + Bindings.Agg.Sum => "SUM", + Bindings.Agg.Avg => "AVG", + Bindings.Agg.Facet => "FACET", + Bindings.Agg.Min => "MIN", + Bindings.Agg.Max => "MAX", + Bindings.Agg.Distinct => "DISTINCT", + _ => throw new NotImplementedException(), + }; + + /// + public IQueryBuilder AggregateAvg(string field) + { + _query.Aggregations.Add(new AggregationsDef + { + Fields = [field], + Type = GetAggregateType(Bindings.Agg.Avg), + }); + return this; + } + + private sealed class AggregateFacetRequest : IAggregateFacetRequest + { + private readonly AggregationsDef _agg; + + public AggregateFacetRequest(AggregationsDef agg) + { + _agg = agg; + } + + public IAggregateFacetRequest Limit(int limit) + { + _agg.Limit = limit; + return this; + } + + public IAggregateFacetRequest Offset(int offset) + { + _agg.Offset = offset; + return this; + } + + public IAggregateFacetRequest Sort(string field, bool desc) + { + _agg.Sort ??= []; + _agg.Sort.Add(new AggregationsSortDef { Field = field, Desc = desc }); + return this; + } + } + + /// + public IQueryBuilder AggregateFacet(Action aggFacetQuery, params string[] fields) + { + var agg = new AggregationsDef + { + Fields = new(fields), + Type = GetAggregateType(Bindings.Agg.Facet) + }; + _query.Aggregations.Add(agg); + aggFacetQuery(new AggregateFacetRequest(agg)); + return this; + } + + /// + public IQueryBuilder AggregateMax(string field) + { + _query.Aggregations.Add(new AggregationsDef + { + Fields = [field], + Type = GetAggregateType(Bindings.Agg.Max), + }); + return this; + } + /// + + public IQueryBuilder AggregateMin(string field) + { + _query.Aggregations.Add(new AggregationsDef + { + Fields = [field], + Type = GetAggregateType(Bindings.Agg.Min), + }); + return this; + } + /// + + public IQueryBuilder AggregateSum(string field) + { + _query.Aggregations.Add(new AggregationsDef + { + Fields = [field], + Type = GetAggregateType(Bindings.Agg.Sum), + }); + return this; + } + /// + + public IQueryBuilder And() + { + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder CachedTotal(params string[] totalNames) + { + throw new NotImplementedException(); + } + /// + + public ReadOnlySpan CloseQuery() + { + return _serializer.Serialize(_query); + } + /// + + public void Dispose() + { + } + /// + + public IQueryBuilder Distinct(string distinctIndex) + { + _query.Aggregations.Add(new AggregationsDef + { + Fields = [distinctIndex], + Type = GetAggregateType(Bindings.Agg.Distinct), + }); + return this; + } + /// + + public IQueryBuilder DWithin(string index, (double start, double end) point, double distance) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = Condition.DWITHIN.ToString("g"), Value = distance, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder EqualPosition(params string[] fields) + { + _query.EqualPositions = new(fields); + return this; + } + /// + + public IQueryBuilder Explain() + { + _query.Explain = true; + return this; + } + /// + + public IQueryBuilder Functions(params string[] fields) + { + _query.SelectFunctions = new(fields); + return this; + } + + private string GetJsonJoinType(Bindings.JoinType joinType) => joinType switch + { + Bindings.JoinType.LeftJoin => "LEFT", + Bindings.JoinType.InnerJoin => "INNER", + Bindings.JoinType.OrInnerJoin => "ORINNER", + Bindings.JoinType.Merge => throw new NotImplementedException(), + _ => throw new NotImplementedException() + }; + /// + + public IQueryBuilder InnerJoin(string otherNamespace, Action otherQuery, string field) + { + _query.Filters ??= []; + var otherQueryBuilder = new SerializableQueryBuilder(_serializer, otherNamespace); + otherQuery(otherQueryBuilder); + + var joinType = Bindings.JoinType.InnerJoin; + if (_nextOp == Bindings.Op.Or) + { + _nextOp = Bindings.Op.And; + joinType = Bindings.JoinType.OrInnerJoin; + } + + _query.Filters.Add(new FilterDef + { + Field = field, + JoinQuery = new JoinedDef + { + Namespace = otherQueryBuilder._namespace, + Filters = otherQueryBuilder._query.Filters, + Limit = otherQueryBuilder._query.Limit, + Offset = otherQueryBuilder._query.Offset, + Sort = otherQueryBuilder._query.Sort.FirstOrDefault(), + Type = GetJsonJoinType(joinType), + On = otherQueryBuilder._onDefs + } + }); + + return this; + } + /// + + public IQueryBuilder Join(string otherNamespace, Action otherQuery, string field) + { + return LeftJoin(otherNamespace, otherQuery, field); + } + /// + + public IQueryBuilder LeftJoin(string otherNamespace, Action otherQuery, string field) + { + _query.Filters ??= []; + var otherQueryBuilder = new SerializableQueryBuilder(_serializer, otherNamespace); + otherQuery(otherQueryBuilder); + _query.Filters.Add(new FilterDef + { + Field = field, + JoinQuery = new JoinedDef + { + Namespace = otherQueryBuilder._namespace, + Filters = otherQueryBuilder._query.Filters, + Limit = otherQueryBuilder._query.Limit, + Offset = otherQueryBuilder._query.Offset, + Sort = otherQueryBuilder._query.Sort.FirstOrDefault(), + Type = GetJsonJoinType(Bindings.JoinType.LeftJoin), + On = otherQueryBuilder._onDefs + } + }); + + return this; + } + /// + + public IQueryBuilder Limit(int limitItems) + { + _query.Limit = limitItems; + return this; + } + /// + + public IQueryBuilder Match(string index, params string[] keys) + { + return WhereString(index, Condition.EQ, keys); + } + /// + + public IQueryBuilder Merge(string otherNamespace, Action otherQuery) + { + _query.MergeQueries ??= []; + var otherQueryBuilder = new SerializableQueryBuilder(_serializer, otherNamespace); + otherQuery(otherQueryBuilder); + _query.MergeQueries.Add(otherQueryBuilder._query); + return this; + } + /// + + public IQueryBuilder Not() + { + _nextOp = Bindings.Op.Not; + return this; + } + /// + + public IQueryBuilder Offset(int startOffset) + { + _query.Offset = startOffset; + return this; + } + + private readonly List _onDefs = []; + /// + public IQueryBuilder On(string index, Condition condition, string joinIndex) + { + _onDefs.Add(new OnDef { LeftField = index, Cond = condition.ToString("g"), RightField = joinIndex, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder Or() + { + _nextOp = Bindings.Op.Or; + return this; + } + /// + + public IQueryBuilder ReqTotal(params string[] totalNames) + { + if (totalNames.Length != 0) + { + TotalName = totalNames[0]; + _query.ReqTotal = TotalName; + } + return this; + } + /// + + public IQueryBuilder Select(params string[] fields) + { + _query.SelectFilter = new(fields); + return this; + } + /// + + public IQueryBuilder Sort(string sortIndex, bool desc, params object[] values) + { + _query.Sort ??= []; + _query.Sort.Add(new SortDef { Field = sortIndex, Desc = desc, Values = new(values) }); + return this; + } + /// + + public IQueryBuilder SortStFieldDistance(string field1, string field2, bool desc) + { + throw new NotImplementedException(); + } + /// + + public IQueryBuilder SortStPointDistance(string field, (double X, double Y) p, bool desc) + { + throw new NotImplementedException(); + } + + private string GetStrictMode(QueryStrictMode mode) => mode switch + { + QueryStrictMode.QueryStrictModeNotSet => null, + QueryStrictMode.QueryStrictModeNone => "none", + QueryStrictMode.QueryStrictModeNames => "names", + QueryStrictMode.QueryStrictModeIndexes => "indexes", + _ => throw new NotImplementedException(), + }; + /// + + public IQueryBuilder Strict(QueryStrictMode mode) + { + _query.StrictMode = GetStrictMode(mode); + return this; + } + /// + + public IQueryBuilder Where(Action filterQuery) + { + _query.Filters ??= []; + var subQueryBuilder = new SerializableQueryBuilder(_serializer, _namespace); + filterQuery(subQueryBuilder); + if (subQueryBuilder._query.Filters != null) + { + _query.Filters.Add(new FilterDef + { + Filters = subQueryBuilder._query.Filters, + Op = _nextOp?.ToString("g") + }); + } + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder Where(string index, Condition condition, object keys) + { + _query.Filters ??= []; + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereBetweenFields(string firstField, Condition condition, string secondField) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = firstField, Cond = condition.ToString("g"), Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereBool(string index, Condition condition, params bool[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereComposite(string index, Condition condition, params object[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereDouble(string index, Condition condition, params double[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereInt(string index, Condition condition, params int[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereInt32(string index, Condition condition, params int[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereInt64(string index, Condition condition, params long[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereString(string index, Condition condition, params string[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WhereUuid(string index, Condition condition, params string[] keys) + { + _query.Filters ??= new(); + _query.Filters.Add(new FilterDef { Field = index, Cond = condition.ToString("g"), Value = keys, Op = _nextOp?.ToString("g") }); + _nextOp = Bindings.Op.And; + return this; + } + /// + + public IQueryBuilder WithRank() + { + _query.SelectWithRank = true; + return this; + } +} diff --git a/src/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64.csproj b/src/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64.csproj index 0d785ae..a7e7914 100644 --- a/src/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64.csproj +++ b/src/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64/ReindexerNet.Embedded.NativeAssets.AlpineLinux-x64.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Embedded.NativeAssets.Linux-x64/ReindexerNet.Embedded.NativeAssets.Linux-x64.csproj b/src/ReindexerNet.Embedded.NativeAssets.Linux-x64/ReindexerNet.Embedded.NativeAssets.Linux-x64.csproj index 281b36f..9d246ce 100644 --- a/src/ReindexerNet.Embedded.NativeAssets.Linux-x64/ReindexerNet.Embedded.NativeAssets.Linux-x64.csproj +++ b/src/ReindexerNet.Embedded.NativeAssets.Linux-x64/ReindexerNet.Embedded.NativeAssets.Linux-x64.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Embedded.NativeAssets.Osx-x64/ReindexerNet.Embedded.NativeAssets.Osx-x64.csproj b/src/ReindexerNet.Embedded.NativeAssets.Osx-x64/ReindexerNet.Embedded.NativeAssets.Osx-x64.csproj index 0f70f70..049cf78 100644 --- a/src/ReindexerNet.Embedded.NativeAssets.Osx-x64/ReindexerNet.Embedded.NativeAssets.Osx-x64.csproj +++ b/src/ReindexerNet.Embedded.NativeAssets.Osx-x64/ReindexerNet.Embedded.NativeAssets.Osx-x64.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Embedded.NativeAssets.Win-x64/ReindexerNet.Embedded.NativeAssets.Win-x64.csproj b/src/ReindexerNet.Embedded.NativeAssets.Win-x64/ReindexerNet.Embedded.NativeAssets.Win-x64.csproj index 8d4eb28..6658f15 100644 --- a/src/ReindexerNet.Embedded.NativeAssets.Win-x64/ReindexerNet.Embedded.NativeAssets.Win-x64.csproj +++ b/src/ReindexerNet.Embedded.NativeAssets.Win-x64/ReindexerNet.Embedded.NativeAssets.Win-x64.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Embedded.NativeAssets.Win-x86/ReindexerNet.Embedded.NativeAssets.Win-x86.csproj b/src/ReindexerNet.Embedded.NativeAssets.Win-x86/ReindexerNet.Embedded.NativeAssets.Win-x86.csproj index db7b79b..c182097 100644 --- a/src/ReindexerNet.Embedded.NativeAssets.Win-x86/ReindexerNet.Embedded.NativeAssets.Win-x86.csproj +++ b/src/ReindexerNet.Embedded.NativeAssets.Win-x86/ReindexerNet.Embedded.NativeAssets.Win-x86.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Embedded/EmbeddedTransactionInvoker.cs b/src/ReindexerNet.Embedded/EmbeddedTransactionInvoker.cs index eb79ecf..a4c143c 100644 --- a/src/ReindexerNet.Embedded/EmbeddedTransactionInvoker.cs +++ b/src/ReindexerNet.Embedded/EmbeddedTransactionInvoker.cs @@ -4,109 +4,109 @@ using System.Threading.Tasks; using System.Collections.Generic; using System.Threading; +using ReindexerNet.Internal; -namespace ReindexerNet.Embedded +namespace ReindexerNet.Embedded; + +internal class EmbeddedTransactionInvoker : ITransactionInvoker { - internal class EmbeddedTransactionInvoker : ITransactionInvoker - { - private readonly UIntPtr _rx; - private readonly UIntPtr _tr; - private readonly reindexer_ctx_info _ctxInfo; - private readonly IReindexerSerializer _serializer; + private readonly UIntPtr _rx; + private readonly UIntPtr _tr; + private readonly reindexer_ctx_info _ctxInfo; + private readonly IReindexerSerializer _serializer; - public EmbeddedTransactionInvoker(UIntPtr rx, UIntPtr tr, reindexer_ctx_info ctxInfo, IReindexerSerializer serializer) - { - _rx = rx; - _tr = tr; - _ctxInfo = ctxInfo; - _serializer = serializer; - } + public EmbeddedTransactionInvoker(UIntPtr rx, UIntPtr tr, reindexer_ctx_info ctxInfo, IReindexerSerializer serializer) + { + _rx = rx; + _tr = tr; + _ctxInfo = ctxInfo; + _serializer = serializer; + } - public int Commit() + public int Commit() + { + var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_commit_transaction(_rx, _tr, _ctxInfo)); + try { - var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_commit_transaction(_rx, _tr, _ctxInfo)); - try - { - var reader = new CJsonReader(rsp.@out); - var rawQueryParams = reader.ReadRawQueryParams(); - - return rawQueryParams.count; - } - finally - { - rsp.@out.Free(); - } - } + var reader = new CJsonReader(rsp.@out); + var rawQueryParams = reader.ReadRawQueryParams(); - public Task CommitAsync(CancellationToken cancellationToken = default) - { - return Task.FromResult(Commit()); + return rawQueryParams.count; } - - public void ModifyItem(ItemModifyMode mode, ReadOnlySpan itemBytes, SerializerType dataEncoding, string[] precepts = null) + finally { - precepts = precepts ?? new string[0]; - using (var writer = new CJsonWriter()) - { - writer.PutVarCUInt((int)dataEncoding); // format - writer.PutVarCUInt((int)mode);// mode - writer.PutVarCUInt(0);// stateToken - - writer.PutVarCUInt(precepts.Length);// len(precepts) - foreach (var precept in precepts) - { - writer.PutVString(precept); - } - - reindexer_buffer.PinBufferFor(writer.CurrentBuffer, itemBytes, (args, data) => - { - Assert.ThrowIfError(() => ReindexerBinding.reindexer_modify_item_packed_tx(_rx, _tr, args, data)); - }); - } + rsp.@out.Free(); } + } + public Task CommitAsync(CancellationToken cancellationToken = default) + { + return Task.FromResult(Commit()); + } - public int ModifyItems(ItemModifyMode mode, IEnumerable items, string[] precepts = null) + public void ModifyItem(ItemModifyMode mode, ReadOnlySpan itemBytes, SerializerType dataEncoding, string[] precepts = null) + { + precepts = precepts ?? []; + using (var writer = new CJsonWriter()) { - var result = 0; - foreach (var item in items) + writer.PutVarCUInt((int)dataEncoding); // format + writer.PutVarCUInt((int)mode);// mode + writer.PutVarCUInt(0);// stateToken + + writer.PutVarCUInt(precepts.Length);// len(precepts) + foreach (var precept in precepts) { - ModifyItem(mode, _serializer.Serialize(item), _serializer.Type, precepts); - result++; + writer.PutVString(precept); } - return result; - } - public int ModifyItems(ItemModifyMode mode, IEnumerable itemDatas, SerializerType dataEncoding, string[] precepts = null) - { - var result = 0; - foreach (var itemData in itemDatas) + reindexer_buffer.PinBufferFor(writer.CurrentBuffer, itemBytes, (args, data) => { - ModifyItem(mode, itemData, dataEncoding, precepts); - result++; - } - return result; + Assert.ThrowIfError(() => ReindexerBinding.reindexer_modify_item_packed_tx(_rx, _tr, args, data)); + }); } + } - public Task ModifyItemsAsync(ItemModifyMode mode, IEnumerable items, string[] precepts = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(ModifyItems(mode, items, precepts)); - } - public Task ModifyItemsAsync(ItemModifyMode mode, IEnumerable itemDatas, SerializerType dataEncoding, string[] precepts = null, CancellationToken cancellationToken = default) + public int ModifyItems(ItemModifyMode mode, IEnumerable items, string[] precepts = null) + { + var result = 0; + foreach (var item in items) { - return Task.FromResult(ModifyItems(mode, itemDatas, dataEncoding, precepts)); + ModifyItem(mode, _serializer.Serialize(item), _serializer.Type, precepts); + result++; } + return result; + } - public void Rollback() + public int ModifyItems(ItemModifyMode mode, IEnumerable itemDatas, SerializerType dataEncoding, string[] precepts = null) + { + var result = 0; + foreach (var itemData in itemDatas) { - Assert.ThrowIfError(() => ReindexerBinding.reindexer_rollback_transaction(_rx, _tr)); + ModifyItem(mode, itemData, dataEncoding, precepts); + result++; } + return result; + } - public Task RollbackAsync(CancellationToken cancellationToken = default) - { - Rollback(); - return Task.CompletedTask; - } + public Task ModifyItemsAsync(ItemModifyMode mode, IEnumerable items, string[] precepts = null, CancellationToken cancellationToken = default) + { + return Task.FromResult(ModifyItems(mode, items, precepts)); + } + + public Task ModifyItemsAsync(ItemModifyMode mode, IEnumerable itemDatas, SerializerType dataEncoding, string[] precepts = null, CancellationToken cancellationToken = default) + { + return Task.FromResult(ModifyItems(mode, itemDatas, dataEncoding, precepts)); + } + + public void Rollback() + { + Assert.ThrowIfError(() => ReindexerBinding.reindexer_rollback_transaction(_rx, _tr)); + } + + public Task RollbackAsync(CancellationToken cancellationToken = default) + { + Rollback(); + return Task.CompletedTask; } } diff --git a/src/ReindexerNet.Embedded/Internal/BindingHelpers.cs b/src/ReindexerNet.Embedded/Internal/BindingHelpers.cs index b4bf271..207192b 100644 --- a/src/ReindexerNet.Embedded/Internal/BindingHelpers.cs +++ b/src/ReindexerNet.Embedded/Internal/BindingHelpers.cs @@ -1,57 +1,57 @@ using System; using System.Collections.Generic; using System.Text; +using ReindexerNet.Internal; -namespace ReindexerNet.Embedded.Internal +namespace ReindexerNet.Embedded.Internal; + +internal static class BindingHelpers { - internal static class BindingHelpers + public static (string json, List offsets, byte[] explain) RawResultToJson(ReadOnlySpan rawResult, string jsonName, string totalName) { - public static (string json, List offsets, byte[] explain) RawResultToJson(ReadOnlySpan rawResult, string jsonName, string totalName) - { - var ser = new CJsonReader(rawResult); - var rawQueryParams = ser.ReadRawQueryParams(); - var explain = rawQueryParams.explainResults; + var ser = new CJsonReader(rawResult); + var rawQueryParams = ser.ReadRawQueryParams(); + var explain = rawQueryParams.explainResults; - var jsonReserveLen = rawResult.Length + totalName.Length + jsonName.Length + 20; - var jsonBuf = new StringBuilder(jsonReserveLen); + var jsonReserveLen = rawResult.Length + totalName.Length + jsonName.Length + 20; + var jsonBuf = new StringBuilder(jsonReserveLen); - var offsets = new List(rawQueryParams.count); + var offsets = new List(rawQueryParams.count); - jsonBuf.Append("{\""); + jsonBuf.Append("{\""); - if (totalName.Length != 0 && rawQueryParams.totalcount != 0) - { - jsonBuf.Append(totalName); - jsonBuf.Append("\":"); - jsonBuf.Append(rawQueryParams.totalcount); - jsonBuf.Append(",\""); - } + if (totalName.Length != 0 && rawQueryParams.totalcount != 0) + { + jsonBuf.Append(totalName); + jsonBuf.Append("\":"); + jsonBuf.Append(rawQueryParams.totalcount); + jsonBuf.Append(",\""); + } - jsonBuf.Append(jsonName); - jsonBuf.Append("\":["); + jsonBuf.Append(jsonName); + jsonBuf.Append("\":["); - for (var i = 0; i < rawQueryParams.count; i++) + for (var i = 0; i < rawQueryParams.count; i++) + { + var item = ser.ReadRawItemParams(); + if (i != 0) { - var item = ser.ReadRawItemParams(); - if (i != 0) - { - jsonBuf.Append(","); - } - offsets.Add(jsonBuf.Length); - jsonBuf.Append(Encoding.UTF8.GetString(item.data + jsonBuf.Append(","); + } + offsets.Add(jsonBuf.Length); + jsonBuf.Append(Encoding.UTF8.GetString(item.data #if NETSTANDARD2_0 || NET472 - .ToArray() + .ToArray() #endif - )); + )); - if ((rawQueryParams.flags & CJsonReader.ResultsWithJoined) != 0 && ser.GetVarUInt() != 0) - { - throw new NotImplementedException("Sorry, not implemented: Can't return join query results as json"); - } + if ((rawQueryParams.flags & CJsonReader.ResultsWithJoined) != 0 && ser.GetVarUInt() != 0) + { + throw new NotImplementedException("Sorry, not implemented: Can't return join query results as json"); } - jsonBuf.Append("]}"); - - return (jsonBuf.ToString(), offsets, explain.ToArray()); } + jsonBuf.Append("]}"); + + return (jsonBuf.ToString(), offsets, explain.ToArray()); } } diff --git a/src/ReindexerNet.Embedded/Internal/CJsonReader.cs b/src/ReindexerNet.Embedded/Internal/CJsonReader.cs deleted file mode 100644 index e488c8c..0000000 --- a/src/ReindexerNet.Embedded/Internal/CJsonReader.cs +++ /dev/null @@ -1,379 +0,0 @@ -#pragma warning disable S1481 // Unused local variables should be removed -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace ReindexerNet.Embedded.Internal -{ - ref struct CJsonReader - { - public const int ResultsFormatMask = 0xF; - public const int ResultsPure = 0x0; - public const int ResultsPtrs = 0x1; - public const int ResultsCJson = 0x2; - public const int ResultsJson = 0x3; - - public const int ResultsWithPayloadTypes = 0x10; - public const int ResultsWithItemID = 0x20; - public const int ResultsWithPercents = 0x40; - public const int ResultsWithNsID = 0x80; - public const int ResultsWithJoined = 0x100; - - - private readonly ReadOnlySpan _buffer; - private int _flags { get; set; } - private int _pos { get; set; } - - public CJsonReader(ReadOnlySpan buffer) - { - _buffer = buffer; - _flags = 0; - _pos = 0; - } - - public RawResultItemParams ReadRawItemParams() - { - var v = new RawResultItemParams(); - if ((_flags & ResultsWithItemID) != 0) - { - v.id = (int)GetVarUInt(); - v.version = (int)GetVarUInt(); - } - - if ((_flags & ResultsWithNsID) != 0) - { - v.nsid = (int)GetVarUInt(); - } - - if ((_flags & ResultsWithPercents) != 0) - { - v.proc = (int)GetVarUInt(); - } - - switch (_flags & ResultsFormatMask) - { - case ResultsPure: - case ResultsPtrs: - v.cptr = (UIntPtr)GetUInt64(); - break; - case ResultsJson: - case ResultsCJson: - v.data = GetBytes(); - break; - } - return v; - } - - public RawResultQueryParams ReadRawQueryParams(/*Action updatePayloadType = null*/) - { - var v = new RawResultQueryParams(); - v.aggResults = new List(); - v.flags = (int)GetVarUInt(); - v.totalcount = (int)GetVarUInt(); - v.qcount = (int)GetVarUInt(); - v.count = (int)GetVarUInt(); - - if ((v.flags & ResultsWithPayloadTypes) != 0) - { - var ptCount = (int)GetVarUInt(); - for (var i = 0; i < ptCount; i++) - { - var nsid = (int)GetVarUInt(); - var nsName = GetVString(); - -#pragma warning disable S125 // Sections of code should not be commented out - //if (updatePayloadType == null) - //{ - // throw new Exception("Internal error: Got payload types from raw query params, but there are no updatePayloadType"); - //} - //updatePayloadType(nsid); -#pragma warning restore S125 // Sections of code should not be commented out - ReadPayloadType(); - } - } - ReadExtraResults(ref v); - _flags = v.flags; - - return v; - } - - public void ReadExtraResults(ref RawResultQueryParams v) - { - while (true) - { - var tag = (QueryResultTag)GetVarUInt(); - if (tag == QueryResultTag.End) - { - break; - } - - var data = GetBytes(); - switch (tag) - { - case QueryResultTag.Explain: - v.explainResults = data; - break; - case QueryResultTag.Aggregation: - v.aggResults.Add(data.ToArray()); - break; - } - } - } - - public void ReadPayloadType() - { - var stateToken = GetVarUInt(); - var version = GetVarUInt(); - -#pragma warning disable S125 // Sections of code should not be commented out - // skip := state.Version >= version && state.StateToken == stateToken - - //if !skip { - // state.StateData = &StateData{Version: version, StateToken: stateToken} - //} - - - //state.tagsMatcher.Read(s, skip) - var tagsCount = (int)GetVarUInt(); - //if !skip { - // tm.Tags = make([]string, tagsCount, tagsCount) - // tm.Names = make(map[string]int) - - // for i := 0; i < tagsCount; i++ { - // tm.Tags[i] = ser.GetVString() - // tm.Names[tm.Tags[i]] = i - // } - //} else { - for (var i = 0; i < tagsCount; i++) - { - var tag = GetVString(); - } - //} - - - //state.payloadType.Read(s, skip) - - var pStringHdrOffset = (UIntPtr)GetVarUInt(); - var fieldsCount = (int)GetVarUInt(); - //fields := make([]payloadFieldType, fieldsCount, fieldsCount) - var fields = new PayloadFieldType[fieldsCount]; - for (var i = 0; i < fieldsCount; i++) - { - var payloadFieldType = new PayloadFieldType(); - payloadFieldType.Type = (int)GetVarUInt(); - payloadFieldType.Name = GetVString(); - payloadFieldType.Offset = (UIntPtr)GetVarUInt(); - payloadFieldType.Size = (UIntPtr)GetVarUInt(); - payloadFieldType.IsArray = GetVarUInt() != 0; - - fields[i] = payloadFieldType; - var jsonPathCnt = GetVarUInt(); - for (; jsonPathCnt != 0; jsonPathCnt--) - { - GetVString(); - } - } - //if !skip { - // pt.Fields = fields - //} -#pragma warning restore S125 // Sections of code should not be commented out - } - - private -#if !NETSTANDARD2_0 && !NET472 - readonly -#else - static -#endif - void checkbound(int pos, int need, int len) - { - if (pos + need > len) - { - throw new InternalBufferOverflowException($"Binary buffer underflow. Need more {need} bytes, pos={pos},len={len}"); - } - } - - private -#if !NETSTANDARD2_0 && !NET472 - readonly -#else - static -#endif - int scan_varint(int len, ReadOnlySpan data) - { - int i; - if (len > 10) len = 10; - for (i = 0; i < len; i++) - if ((data[i] & 0x80) == 0) break; - if (i == len) return 0; - return i + 1; - } - - private -#if !NETSTANDARD2_0 && !NET472 - readonly -#else - static -#endif - uint parse_uint32(uint len, ReadOnlySpan data) - { - uint rv = data[0] & (uint)sbyte.MaxValue; - if (len > 1U) - { - rv |= (uint)((data[1] & sbyte.MaxValue) << 7); - if (len > 2U) - { - rv |= (uint)((data[2] & sbyte.MaxValue) << 14); - if (len > 3U) - { - rv |= (uint)((data[3] & sbyte.MaxValue) << 21); - if (len > 4U) - rv |= (uint)data[4] << 28; - } - } - } - return rv; - } - - private -#if !NETSTANDARD2_0 && !NET472 - readonly -#else - static -#endif - ulong parse_uint64(uint len, ReadOnlySpan data) - { - ulong rv; - if (len < 5U) - { - rv = parse_uint32(len, data); - } - else - { - ulong shifted = (ulong)((long)(data[0] & sbyte.MaxValue) | (long)(data[1] & sbyte.MaxValue) << 7 | (long)(data[2] & sbyte.MaxValue) << 14 | (long)(data[3] & sbyte.MaxValue) << 21); - uint shift = 28; - for (uint index = 4; index < len; ++index) - { - shifted |= (ulong)(data[(int)index] & sbyte.MaxValue) << (int)shift; - shift += 7U; - } - rv = shifted; - } - return rv; - } - - public ulong GetVarUInt() - { - var l = scan_varint(_buffer.Length - _pos, _buffer.Slice(_pos)); - if (l == 0) - { - throw new InvalidDataException($"Binary buffer broken - scan_varint failed: pos={_pos},len={_buffer.Length}"); - } - checkbound(_pos, l, _buffer.Length); - _pos += l; - return parse_uint64((uint)l, _buffer.Slice(_pos - l)); - } - - private long ReadIntBits(int size) - { - long v = 0; - for (var i = size - 1; i >= 0; i--) - { - v = ((long)(_buffer[i + _pos]) & 0xFF) | (v << 8); - } - _pos += size; - return v; - } - - private ulong ReadUIntBits(int size) - { - ulong v = 0; - for (var i = size - 1; i >= 0; i--) - { - v = ((ulong)(_buffer[i + _pos]) & 0xFF) | (v << 8); - } - _pos += size; - return v; - } - - private uint GetUInt32() - { - checkbound(_pos, sizeof(uint), _buffer.Length); - return (uint)ReadIntBits(sizeof(uint)); - } - - private ulong GetUInt64() - { - checkbound(_pos, sizeof(ulong), _buffer.Length); - return ReadUIntBits(sizeof(ulong)); - } - - private ReadOnlySpan GetBytes() - { - var l = (int)GetUInt32(); - if (_pos + l > _buffer.Length) - { - throw new InternalBufferOverflowException($"Internal error: serializer need {l} bytes, but only {_buffer.Length - _pos} available"); - } - - var v = _buffer.Slice(_pos, l); - _pos += l; - return v; - } - - private string GetVString() - { - var l = (int)GetVarUInt(); - if (_pos + l > _buffer.Length) - { - throw new InternalBufferOverflowException($"Internal error: serializer need {l} bytes, but only {_buffer.Length - _pos} available"); - } - - var v = Encoding.UTF8.GetString(_buffer.Slice(_pos, l) -#if NETSTANDARD2_0 || NET472 - .ToArray() -#endif - ); - _pos += l; - return v; - } - } - - internal ref struct RawResultItemParams - { - public int id; - public int version; - public int nsid; - public int proc; - public UIntPtr cptr; - public ReadOnlySpan data; - } - - internal class RawResultsExtraParam - { - public int Tag; - public string Name; - public byte[] Data; - } - - internal ref struct RawResultQueryParams - { - public int flags; - public int totalcount; - public int qcount; - public int count; - public List aggResults; - public ReadOnlySpan explainResults; - } - - internal class PayloadFieldType - { - internal int Type; - internal string Name; - internal UIntPtr Offset; - internal UIntPtr Size; - internal bool IsArray; - } -} -#pragma warning restore S1481 // Unused local variables should be removed \ No newline at end of file diff --git a/src/ReindexerNet.Embedded/Internal/CJsonWriter.cs b/src/ReindexerNet.Embedded/Internal/CJsonWriter.cs deleted file mode 100644 index 615898d..0000000 --- a/src/ReindexerNet.Embedded/Internal/CJsonWriter.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -namespace ReindexerNet.Embedded.Internal -{ - internal class CJsonWriter : IDisposable - { - private readonly ArrayPool _pool; - private byte[] _buffer; - private int _pos; - public CJsonWriter() - { - _pool = ArrayPool.Shared; - _buffer = _pool.Rent(100); - } - - public ReadOnlySpan CurrentBuffer => _buffer.AsSpan().Slice(0, _pos); - - private void EnsureRemainingSize(int length) - { - if (_pos + length > _buffer.Length) - { - var oldBufferRef = _buffer; - _buffer = _pool.Rent(_buffer.Length*2); - _pool.Return(oldBufferRef); - } - } - - public void PutVarUInt(ulong v) - { - EnsureRemainingSize(10); - var len = uint64_pack(v, _buffer.AsSpan().Slice(_pos)); - _pos += (int)len; - } - - public void PutVarCUInt(int v) - { - PutVarUInt((ulong)v); - } - - public void PutVString(string v) - { - if (v == null) - { - _buffer[_pos++] = 0; - return; - } - - var strArr = v.AsSpan(); - EnsureRemainingSize(10 + strArr.Length); - var currentPos = _buffer.AsSpan().Slice(_pos); - var lenSize = (int)uint32_pack((uint)strArr.Length, currentPos); - currentPos = currentPos.Slice(lenSize); - for (int i = 0; i < strArr.Length; i++) - { - currentPos[i] = (byte)strArr[i]; - } - _pos += lenSize + strArr.Length; - } - - private static uint uint32_pack(uint value, Span @out) - { - uint rv = 0; - if (value >= 128U) - { - @out[(int)rv] = (byte)((int)value | 128); - ++rv; - value >>= 7; - if (value >= 128U) - { - @out[(int)rv] = (byte)((int)value | 128); - ++rv; - value >>= 7; - if (value >= 128U) - { - @out[(int)rv] = (byte)((int)value | 128); - ++rv; - value >>= 7; - if (value >= 128U) - { - @out[(int)rv] = (byte)((int)value | 128); - ++rv; - value >>= 7; - } - } - } - } - @out[(int)rv * 1] = (byte)value; - return rv + 1U; - } - - private static uint uint64_pack(ulong value, Span @out) - { - uint hi = (uint)(value >> 32); - uint lo = (uint)value; - uint rv; - if (hi == 0U) - { - rv = uint32_pack(lo, @out); - } - else - { - @out[0] = (byte)((int)lo | 128); - @out[1] = (byte)((int)(lo >> 7) | 128); - @out[2] = (byte)((int)(lo >> 14) | 128); - @out[3] = (byte)((int)(lo >> 21) | 128); - if (hi < 8U) - { - @out[4] = (byte)((int)hi << 4 | (int)(lo >> 28)); - rv = 5U; - } - else - { - @out[4] = (byte)(((int)hi & 7) << 4 | (int)(lo >> 28) | 128); - uint num4 = hi >> 3; - uint num5 = 5; - for (; num4 >= 128U; num4 >>= 7) - { - @out[(int)num5 * 1] = (byte)((int)num4 | 128); - ++num5; - } - @out[(int)num5 * 1] = (byte)num4; - rv = num5 + 1U; - } - } - return rv; - } - - #region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - _pool.Return(_buffer); - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - disposedValue = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~CJsonBuffer() - // { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - - } -} diff --git a/src/ReindexerNet.Embedded/Internal/CTypes.cs b/src/ReindexerNet.Embedded/Internal/CTypes.cs index ed0d259..f888272 100644 --- a/src/ReindexerNet.Embedded/Internal/CTypes.cs +++ b/src/ReindexerNet.Embedded/Internal/CTypes.cs @@ -12,253 +12,246 @@ using uint64_t = System.UInt64; using uintptr_t = System.UIntPtr; -namespace ReindexerNet.Embedded.Internal +namespace ReindexerNet.Embedded.Internal; + +[StructLayout(LayoutKind.Sequential)] +struct reindexer_config { - [StructLayout(LayoutKind.Sequential)] - struct reindexer_config - { - int64_t allocator_cache_limit; - float allocator_max_cache_part; - } + int64_t allocator_cache_limit; + float allocator_max_cache_part; +} - [StructLayout(LayoutKind.Sequential)] - struct reindexer_buffer - { - public IntPtr data;//uint8_t* - public int len; +[StructLayout(LayoutKind.Sequential)] +struct reindexer_buffer +{ + public IntPtr data;//uint8_t* + public int len; - public static implicit operator ReadOnlySpan(reindexer_buffer rb) + public static implicit operator ReadOnlySpan(reindexer_buffer rb) + { + unsafe { - unsafe - { - return new ReadOnlySpan((void*)rb.data, rb.len); - } + return new ReadOnlySpan((void*)rb.data, rb.len); } + } - public static ReindexerBufferHandle From(byte[] byteArray) - { - return new ReindexerBufferHandle(byteArray); - } + public static ReindexerBufferHandle From(byte[] byteArray) + { + return new ReindexerBufferHandle(byteArray); + } - public static void PinBufferFor(ReadOnlySpan byteSpan, Action pinnedAction) + public static void PinBufferFor(ReadOnlySpan byteSpan, Action pinnedAction) + { + unsafe { - unsafe + fixed (byte* pData = &MemoryMarshal.GetReference(byteSpan)) { - fixed (byte* pData = &MemoryMarshal.GetReference(byteSpan)) + pinnedAction(new reindexer_buffer { - pinnedAction(new reindexer_buffer - { - data = (IntPtr)pData, - len = byteSpan.Length - }); - } + data = (IntPtr)pData, + len = byteSpan.Length + }); } } + } - public static void PinBufferFor(ReadOnlySpan byteSpan1, ReadOnlySpan byteSpan2, - Action pinnedAction) + public static void PinBufferFor(ReadOnlySpan byteSpan1, ReadOnlySpan byteSpan2, + Action pinnedAction) + { + unsafe { - unsafe + fixed (byte* pData1 = &MemoryMarshal.GetReference(byteSpan1)) + fixed (byte* pData2 = &MemoryMarshal.GetReference(byteSpan2)) { - fixed (byte* pData1 = &MemoryMarshal.GetReference(byteSpan1)) - fixed (byte* pData2 = &MemoryMarshal.GetReference(byteSpan2)) - { - pinnedAction( - new reindexer_buffer - { - data = (IntPtr)pData1, - len = byteSpan1.Length - }, - new reindexer_buffer - { - data = (IntPtr)pData2, - len = byteSpan2.Length - } - ); - } + pinnedAction( + new reindexer_buffer + { + data = (IntPtr)pData1, + len = byteSpan1.Length + }, + new reindexer_buffer + { + data = (IntPtr)pData2, + len = byteSpan2.Length + } + ); } } } +} - internal sealed class ReindexerBufferHandle : IDisposable +internal sealed class ReindexerBufferHandle : IDisposable +{ + private readonly GCHandle _gcHandle; + private readonly MemoryHandle _memoryHandle; + + public ReindexerBufferHandle(byte[] byteArray) { - private readonly GCHandle _gcHandle; - private readonly MemoryHandle _memoryHandle; + _gcHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); + Buffer = new reindexer_buffer + { + data = _gcHandle.AddrOfPinnedObject(), + len = byteArray?.Length ?? 0 + }; + } - public ReindexerBufferHandle(byte[] byteArray) + public ReindexerBufferHandle(Memory byteMem) + { + _memoryHandle = byteMem.Pin(); + unsafe { - _gcHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); Buffer = new reindexer_buffer { - data = _gcHandle.AddrOfPinnedObject(), - len = byteArray?.Length ?? 0 + data = (IntPtr)_memoryHandle.Pointer, + len = byteMem.Length }; } + } - public ReindexerBufferHandle(Memory byteMem) - { - _memoryHandle = byteMem.Pin(); - unsafe - { - Buffer = new reindexer_buffer - { - data = (IntPtr)_memoryHandle.Pointer, - len = byteMem.Length - }; - } - } + public reindexer_buffer Buffer { get; private set; } - public reindexer_buffer Buffer { get; private set; } + public static implicit operator reindexer_buffer(ReindexerBufferHandle handle) + { + return handle.Buffer; + } - public static implicit operator reindexer_buffer(ReindexerBufferHandle handle) - { - return handle.Buffer; - } + public void Dispose() + { + _gcHandle.Free(); + _memoryHandle.Dispose(); + } +} - public void Dispose() - { - _gcHandle.Free(); - _memoryHandle.Dispose(); - } +[StructLayout(LayoutKind.Sequential)] +struct reindexer_resbuffer +{ + public uintptr_t results_ptr; + public uintptr_t data; + public int len; + + public void Free() + { + ReindexerBinding.FreeBuffer(this); } - [StructLayout(LayoutKind.Sequential)] - struct reindexer_resbuffer + public static implicit operator ReadOnlySpan(reindexer_resbuffer rb) { - public uintptr_t results_ptr; - public uintptr_t data; - public int len; + if (rb.data == UIntPtr.Zero || rb.len == 0) + return []; - public void Free() + unsafe { - ReindexerBinding.FreeBuffer(this); - } - - public static implicit operator ReadOnlySpan(reindexer_resbuffer rb) - { - if (rb.data == UIntPtr.Zero || rb.len == 0) - return ReadOnlySpan.Empty; - - unsafe - { - return new ReadOnlySpan((byte*)rb.data, rb.len); - } + return new ReadOnlySpan((byte*)rb.data, rb.len); } + } - public static implicit operator string(reindexer_resbuffer rb) + public static implicit operator string(reindexer_resbuffer rb) + { + unsafe { - unsafe - { - var ptr = (IntPtr)(byte*)rb.data; - var result = Marshal.PtrToStringAnsi(ptr); - ReindexerBinding.reindexer_malloc_free(ptr); - return result; - } + var ptr = (IntPtr)(byte*)rb.data; + var result = Marshal.PtrToStringAnsi(ptr); + ReindexerBinding.reindexer_malloc_free(ptr); + return result; } } +} - [StructLayout(LayoutKind.Sequential)] - struct reindexer_error +[StructLayout(LayoutKind.Sequential)] +struct reindexer_error +{ + public IntPtr what; //const char* + public int code; +} + +[StructLayout(LayoutKind.Sequential)] +struct reindexer_string +{ + public IntPtr p;//void* => reindexer içeride const char*'a cast ediyor. + public int32_t n; //bunu içeride Span.Slice gibi kullanıyor. Belki p ReadOnlySpan da olabilir. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public int8_t[] reserved; + + public static ReindexerStringHandle From(byte[] byteArray) { - public IntPtr what; //const char* - public int code; + return new ReindexerStringHandle(byteArray); } - [StructLayout(LayoutKind.Sequential)] - struct reindexer_string + public static ReindexerStringHandle From(string utf8Str) { - public IntPtr p;//void* => reindexer içeride const char*'a cast ediyor. - public int32_t n; //bunu içeride Span.Slice gibi kullanıyor. Belki p ReadOnlySpan da olabilir. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - public int8_t[] reserved; + return new ReindexerStringHandle(utf8Str != null ? Encoding.UTF8.GetBytes(utf8Str) : null); + } +} - public static ReindexerStringHandle From(byte[] byteArray) - { - return new ReindexerStringHandle(byteArray); - } +internal sealed class ReindexerStringHandle : IDisposable +{ + private readonly GCHandle _gcHandle; + private readonly MemoryHandle _memoryHandle; - public static ReindexerStringHandle From(string utf8Str) + public ReindexerStringHandle(byte[] byteArray) + { + _gcHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); + RxString = new reindexer_string { - return new ReindexerStringHandle(utf8Str != null ? Encoding.UTF8.GetBytes(utf8Str) : null); - } + p = _gcHandle.AddrOfPinnedObject(), + n = byteArray?.Length ?? 0 + }; } - internal sealed class ReindexerStringHandle : IDisposable + public ReindexerStringHandle(Memory byteMem) { - private readonly GCHandle _gcHandle; - private readonly MemoryHandle _memoryHandle; - - public ReindexerStringHandle(byte[] byteArray) + _memoryHandle = byteMem.Pin(); + unsafe { - _gcHandle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); RxString = new reindexer_string { - p = _gcHandle.AddrOfPinnedObject(), - n = byteArray?.Length ?? 0 + p = (IntPtr)_memoryHandle.Pointer, + n = byteMem.Length }; } - - public ReindexerStringHandle(Memory byteMem) - { - _memoryHandle = byteMem.Pin(); - unsafe - { - RxString = new reindexer_string - { - p = (IntPtr)_memoryHandle.Pointer, - n = byteMem.Length - }; - } - } - - public reindexer_string RxString { get; private set; } - - public static implicit operator reindexer_string(ReindexerStringHandle handle) - { - return handle.RxString; - } - - public void Dispose() - { - _gcHandle.Free(); - _memoryHandle.Dispose(); - } } - [StructLayout(LayoutKind.Sequential)] - struct reindexer_ret - { - public reindexer_resbuffer @out; - public int err_code; - } + public reindexer_string RxString { get; private set; } - [StructLayout(LayoutKind.Sequential)] - struct reindexer_tx_ret + public static implicit operator reindexer_string(ReindexerStringHandle handle) { - public uintptr_t tx_id; - public reindexer_error err; + return handle.RxString; } - [StructLayout(LayoutKind.Sequential)] - struct reindexer_ctx_info + public void Dispose() { - public uint64_t ctx_id; // 3 most significant bits will be used as flags and discarded - public int64_t exec_timeout; //todo: DateTimeOffset olabilir, araştır. + _gcHandle.Free(); + _memoryHandle.Dispose(); } +} - enum ctx_cancel_type - { - cancel_expilicitly, - cancel_on_timeout - } +[StructLayout(LayoutKind.Sequential)] +struct reindexer_ret +{ + public reindexer_resbuffer @out; + public int err_code; +} - enum QueryResultTag - { - End = 0, - Aggregation = 1, - Explain = 2 - } +[StructLayout(LayoutKind.Sequential)] +struct reindexer_tx_ret +{ + public uintptr_t tx_id; + public reindexer_error err; +} + +[StructLayout(LayoutKind.Sequential)] +struct reindexer_ctx_info +{ + public uint64_t ctx_id; // 3 most significant bits will be used as flags and discarded + public int64_t exec_timeout; //todo: DateTimeOffset olabilir, araştır. +} + +enum ctx_cancel_type +{ + cancel_expilicitly, + cancel_on_timeout } + #pragma warning restore S101 // Types should be named in PascalCase #pragma warning restore RCS1018 // Add accessibility modifiers. #pragma warning restore IDE1006 // Naming Styles diff --git a/src/ReindexerNet.Embedded/Internal/Helpers/TypeHelper.cs b/src/ReindexerNet.Embedded/Internal/Helpers/TypeHelper.cs index 71975cc..e584bc8 100644 --- a/src/ReindexerNet.Embedded/Internal/Helpers/TypeHelper.cs +++ b/src/ReindexerNet.Embedded/Internal/Helpers/TypeHelper.cs @@ -6,7 +6,7 @@ namespace ReindexerNet.Embedded.Internal.Helpers { internal static class TypeHelper { - public static ReindexerStringHandle GetHandle(this string str) + public static ReindexerStringHandle GetStringHandle(this string str) { return reindexer_string.From(str); } diff --git a/src/ReindexerNet.Embedded/Internal/ReindexerBinding.cs b/src/ReindexerNet.Embedded/Internal/ReindexerBinding.cs index 1955667..a7b36dc 100644 --- a/src/ReindexerNet.Embedded/Internal/ReindexerBinding.cs +++ b/src/ReindexerNet.Embedded/Internal/ReindexerBinding.cs @@ -23,7 +23,7 @@ namespace ReindexerNet.Embedded.Internal; internal static class ReindexerBinding { - public const string ReindexerVersion = "v3.12.0"; + public const string ReindexerVersion = "v3.20.0"; private const string BindingLibrary = "reindexer_embedded_server"; private static class Windows @@ -465,14 +465,14 @@ static ReindexerBinding() } #pragma warning restore S3963 // "static" fields should be initialized inline - private static readonly HashSet _searchBinPaths = new HashSet{ + private static readonly HashSet _searchBinPaths = [ $"{AppDomain.CurrentDomain?.BaseDirectory ?? ""}\\bin", Path.GetDirectoryName(Assembly.GetExecutingAssembly()?.Location ?? "."), Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? "."), Directory.GetCurrentDirectory(), "bin", "" - }; + ]; private static void LoadAndBindNativeLibrary() { diff --git a/src/ReindexerNet.Embedded/ReindexerEmbedded.cs b/src/ReindexerNet.Embedded/ReindexerEmbedded.cs index a342592..31de8d5 100644 --- a/src/ReindexerNet.Embedded/ReindexerEmbedded.cs +++ b/src/ReindexerNet.Embedded/ReindexerEmbedded.cs @@ -2,6 +2,7 @@ #pragma warning disable S4136 // Method overloads should be grouped together using ReindexerNet.Embedded.Internal.Helpers; using ReindexerNet.Embedded.Internal; +using ReindexerNet.Internal; using System; using System.Collections.Generic; using System.IO; @@ -10,6 +11,7 @@ using System.Text.Json; using System.Linq; using System.Threading; +using System.Linq.Expressions; namespace ReindexerNet.Embedded; @@ -49,6 +51,8 @@ public ReindexerEmbedded(string dbPath, IReindexerSerializer serializer = null) DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; + private static readonly ReindexerJsonSerializer _defaultJsonSerializer = new(); + private static byte[] InternalSerializeJson(T obj) { return JsonSerializer.SerializeToUtf8Bytes(obj, _jsonSerializerOptions); @@ -86,9 +90,9 @@ public static void DisableLogger() /// public void AddIndex(string nsName, Index indexDefinition) { - using var nsNameRx = nsName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); if (indexDefinition.JsonPaths == null || indexDefinition.JsonPaths.Count == 0) - indexDefinition.JsonPaths = new List { indexDefinition.Name }; + indexDefinition.JsonPaths = [indexDefinition.Name]; using var jsonRx = ReindexerEmbedded.InternalSerializeJson(indexDefinition).GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_add_index(Rx, nsNameRx, jsonRx, _ctxInfo) @@ -105,7 +109,7 @@ public Task AddIndexAsync(string nsName, Index indexDefinition, CancellationToke /// public void CloseNamespace(string nsName) { - using var nsNameRx = nsName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_close_namespace(Rx, nsNameRx, _ctxInfo)); } @@ -125,8 +129,8 @@ public virtual void Connect(ConnectionOptions options = null) Directory.CreateDirectory(_connectionString.DatabaseName); //reindexer sometimes throws permission exception from c++ mkdir func. so we try to crate directory before. } - using var dsn = $"builtin://{_connectionString.DatabaseName}".GetHandle(); - using var version = ReindexerBinding.ReindexerVersion.GetHandle(); + using var dsn = $"builtin://{_connectionString.DatabaseName}".GetStringHandle(); + using var version = ReindexerBinding.ReindexerVersion.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_connect(Rx, dsn, @@ -145,8 +149,8 @@ public Task ConnectAsync(ConnectionOptions options = null, CancellationToken can /// public void DropIndex(string nsName, string indexName) { - using var nsNameRx = nsName.GetHandle(); - using var inameRx = indexName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); + using var inameRx = indexName.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_drop_index(Rx, nsNameRx, inameRx, _ctxInfo) ); @@ -162,7 +166,7 @@ public Task DropIndexAsync(string nsName, string indexName, CancellationToken ca /// public void DropNamespace(string nsName) { - using var nsNameRx = nsName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_drop_namespace(Rx, nsNameRx, _ctxInfo) ); @@ -178,7 +182,7 @@ public Task DropNamespaceAsync(string nsName, CancellationToken cancellationToke /// public void OpenNamespace(string nsName, NamespaceOptions options = null) { - using (var nsNameRx = nsName.GetHandle()) + using (var nsNameRx = nsName.GetStringHandle()) Assert.ThrowIfError(() => { reindexer_error rsp = default; @@ -218,8 +222,8 @@ public Task PingAsync(CancellationToken cancellationToken = default) /// public void RenameNamespace(string oldName, string newName) { - using var oldNameRx = oldName.GetHandle(); - using var newNameRx = newName.GetHandle(); + using var oldNameRx = oldName.GetStringHandle(); + using var newNameRx = newName.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_rename_namespace(Rx, oldNameRx, newNameRx, _ctxInfo)); } @@ -235,7 +239,7 @@ public Task RenameNamespaceAsync(string oldName, string newName, CancellationTok public ReindexerTransaction StartTransaction(string nsName) { UIntPtr tr = UIntPtr.Zero; - using (var nsNameRx = nsName.GetHandle()) + using (var nsNameRx = nsName.GetStringHandle()) Assert.ThrowIfError(() => { var rsp = ReindexerBinding.reindexer_start_transaction(Rx, nsNameRx); @@ -254,7 +258,7 @@ public Task StartTransactionAsync(string nsName, Cancellat /// public void TruncateNamespace(string nsName) { - using var nsNameRx = nsName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_truncate_namespace(Rx, nsNameRx, _ctxInfo) ); @@ -270,9 +274,9 @@ public Task TruncateNamespaceAsync(string nsName, CancellationToken cancellation /// public void UpdateIndex(string nsName, Index indexDefinition) { - using var nsNameRx = nsName.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); if (indexDefinition.JsonPaths == null || indexDefinition.JsonPaths.Count == 0) - indexDefinition.JsonPaths = new List { indexDefinition.Name }; + indexDefinition.JsonPaths = [indexDefinition.Name]; using var jsonRx = ReindexerEmbedded.InternalSerializeJson(indexDefinition).GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_update_index(Rx, nsNameRx, jsonRx, _ctxInfo) @@ -291,7 +295,7 @@ public Task UpdateIndexAsync(string nsName, Index indexDefinition, CancellationT public int ModifyItem(string nsName, ItemModifyMode mode, ReadOnlySpan itemBytes, SerializerType dataEncoding, string[] precepts = null) { var result = 0; - precepts = precepts ?? new string[0]; + precepts = precepts ?? []; using (var writer = new CJsonWriter()) { writer.PutVString(nsName); @@ -364,40 +368,52 @@ public Task ModifyItemsAsync(string nsName, ItemModifyMode mode, IEnumerabl } /// - public QueryItemsOf ExecuteSql(string sql) + public QueryItemsOf Execute(string @namespace, Action query) { + using var builder = new CJsonQueryBuilder(_defaultJsonSerializer, @namespace); + query(builder); + var buffer = builder.CloseQuery(); + var result = new QueryItemsOf { - Items = new List() + Items = [] }; - using var sqlRx = sql.GetHandle(); - var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_select(Rx, sqlRx, 1, new int[0], 0, _ctxInfo)); - try + reindexer_buffer.PinBufferFor(buffer, queryRx => { - var reader = new CJsonReader(rsp.@out); - var rawQueryParams = reader.ReadRawQueryParams(); - var explain = rawQueryParams.explainResults; - - result.QueryTotalItems = rawQueryParams.totalcount != 0 ? rawQueryParams.totalcount : rawQueryParams.count; - if (explain.Length > 0) + var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_select_query(Rx, queryRx, 1, [], 0, _ctxInfo)); + try { - result.Explain = JsonSerializer.Deserialize(explain, _jsonSerializerOptions); + GetItemsFromReindexerResult(result, rsp); } - - for (var i = 0; i < rawQueryParams.count; i++) + finally { - var item = reader.ReadRawItemParams(); - if (item.data.Length > 0) - result.Items.Add(Serializer.Deserialize(item.data)); + rsp.@out.Free(); } + }); - if ((rawQueryParams.flags & CJsonReader.ResultsWithJoined) != 0 && reader.GetVarUInt() != 0) - { - throw new NotImplementedException("Sorry, not implemented: Can't return join query results as json"); - } + return result; + } + + /// + public Task> ExecuteAsync(string @namespace, Action query, CancellationToken cancellationToken = default) + { + return Task.FromResult(Execute(@namespace, query)); + } - return result; + /// + public QueryItemsOf Execute(byte[] query, SerializerType queryEncoding) + { + var result = new QueryItemsOf + { + Items = [] + }; + + using var queryRx = query.GetHandle(); + var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_select_query(Rx, queryRx, 1, [], 0, _ctxInfo)); + try + { + return GetItemsFromReindexerResult(result, rsp); } finally { @@ -405,18 +421,83 @@ public QueryItemsOf ExecuteSql(string sql) } } + /// + public QueryItemsOf ExecuteSql(string sql) + { + var result = new QueryItemsOf + { + Items = [] + }; + + using var sqlRx = sql.GetStringHandle(); + var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_select(Rx, sqlRx, 1, [], 0, _ctxInfo)); + try + { + return GetItemsFromReindexerResult(result, rsp); + } + finally + { + rsp.@out.Free(); + } + } + + private QueryItemsOf GetItemsFromReindexerResult(QueryItemsOf result, reindexer_ret rsp) + { + var reader = new CJsonReader(rsp.@out); + var rawQueryParams = reader.ReadRawQueryParams(); + var explain = rawQueryParams.explainResults; + + result.QueryTotalItems = rawQueryParams.totalcount != 0 ? rawQueryParams.totalcount : rawQueryParams.count; + if (explain.Length > 0) + { + result.Explain = JsonSerializer.Deserialize(explain, _jsonSerializerOptions); + } + + for (var i = 0; i < rawQueryParams.count; i++) + { + var item = reader.ReadRawItemParams(); + if (item.data.Length > 0) + result.Items.Add(Serializer.Deserialize(item.data)); + } + + if ((rawQueryParams.flags & CJsonReader.ResultsWithJoined) != 0 && reader.GetVarUInt() != 0) + { + throw new NotImplementedException("Sorry, not implemented: Can't return join query results as json"); + } + + return result; + } + + /// + public Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default) + { + return Task.FromResult(Execute(query, queryEncoding)); + } + /// public Task> ExecuteSqlAsync(string sql, CancellationToken cancellationToken = default) { return Task.FromResult(ExecuteSql(sql)); } + /// + public QueryItemsOf Execute(byte[] query, SerializerType queryEncoding) + { + return Execute(query, queryEncoding); + } + /// public QueryItemsOf ExecuteSql(string sql) { return ExecuteSql(sql); } + /// + public Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default) + { + return Task.FromResult(Execute(query, queryEncoding)); + } + /// public Task> ExecuteSqlAsync(string sql, CancellationToken cancellationToken = default) { @@ -475,8 +556,8 @@ public Task DeleteAsync(string nsName, IEnumerable items, string[] pr public void CreateDatabase(string dbName) { var newRx = ReindexerBinding.init_reindexer(); - using (var dsn = $"builtin://{dbName}".GetHandle()) - using (var version = ReindexerBinding.ReindexerVersion.GetHandle()) + using (var dsn = $"builtin://{dbName}".GetStringHandle()) + using (var version = ReindexerBinding.ReindexerVersion.GetStringHandle()) Assert.ThrowIfError(() => ReindexerBinding.reindexer_connect(newRx, dsn, @@ -521,7 +602,7 @@ public IEnumerable EnumNamespaces(string name = null, bool onlyNames { query.AppendFormat(" WHERE {0}", string.Join(" AND ", filters)); } - + return ExecuteSql(query.ToString()).Items .Where(ns => ns.Storage == null || withClosed || ns.Storage.Enabled == true); } @@ -536,8 +617,8 @@ public Task> EnumNamespacesAsync(string name = null, bool /// public void SetSchema(string nsName, string jsonSchema) { - using var nsNameRx = nsName.GetHandle(); - using var jsonSchemaRx = jsonSchema.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); + using var jsonSchemaRx = jsonSchema.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_set_schema(Rx, nsNameRx, jsonSchemaRx, _ctxInfo)); @@ -553,8 +634,8 @@ public Task SetSchemaAsync(string nsName, string jsonSchema, CancellationToken c /// public string GetMeta(string nsName, MetaInfo metadata) { - using var nsNameRx = nsName.GetHandle(); - using var keyRx = metadata.Key.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); + using var keyRx = metadata.Key.GetStringHandle(); var rsp = Assert.ThrowIfError(() => ReindexerBinding.reindexer_get_meta(Rx, nsNameRx, keyRx, _ctxInfo)); try { @@ -575,9 +656,9 @@ public Task GetMetaAsync(string nsName, MetaInfo metadata, CancellationT /// public void PutMeta(string nsName, MetaInfo metadata) { - using var nsNameRx = nsName.GetHandle(); - using var keyRx = metadata.Key.GetHandle(); - using var dataRx = metadata.Value.GetHandle(); + using var nsNameRx = nsName.GetStringHandle(); + using var keyRx = metadata.Key.GetStringHandle(); + using var dataRx = metadata.Value.GetStringHandle(); Assert.ThrowIfError(() => ReindexerBinding.reindexer_put_meta(Rx, nsNameRx, keyRx, dataRx, _ctxInfo)); } diff --git a/src/ReindexerNet.Embedded/ReindexerEmbeddedServer.cs b/src/ReindexerNet.Embedded/ReindexerEmbeddedServer.cs index 5cf7347..9440be0 100644 --- a/src/ReindexerNet.Embedded/ReindexerEmbeddedServer.cs +++ b/src/ReindexerNet.Embedded/ReindexerEmbeddedServer.cs @@ -94,7 +94,7 @@ public void Start(string serverConfigYaml, string dbName, string user = null, st try { DebugHelper.Log("Starting reindexer server..."); - using (var configYaml = serverConfigYaml.GetHandle()) + using (var configYaml = serverConfigYaml.GetStringHandle()) ReindexerBinding.start_reindexer_server(_pServer, configYaml); } catch (Exception e) @@ -121,9 +121,9 @@ public void Start(string serverConfigYaml, string dbName, string user = null, st Thread.Sleep(100); } DebugHelper.Log("Reindexer server is started."); - using (var dbNameRx = dbName.GetHandle()) - using (var userRx = user.GetHandle()) - using (var passRx = pass.GetHandle()) + using (var dbNameRx = dbName.GetStringHandle()) + using (var userRx = user.GetStringHandle()) + using (var passRx = pass.GetStringHandle()) Assert.ThrowIfError(() => ReindexerBinding.get_reindexer_instance(_pServer, dbNameRx, userRx, passRx, ref Rx)); } diff --git a/src/ReindexerNet.Embedded/ReindexerNet.Embedded.csproj b/src/ReindexerNet.Embedded/ReindexerNet.Embedded.csproj index 4e2f808..ecda380 100644 --- a/src/ReindexerNet.Embedded/ReindexerNet.Embedded.csproj +++ b/src/ReindexerNet.Embedded/ReindexerNet.Embedded.csproj @@ -7,9 +7,9 @@ ReindexerNet.Embedded Reindexer Embedded library to embed and run in .net projects. reindexer embedded document-db in-memory - 0.3.10.3200 - 0.3.10 - 0.3.10 + 0.4.0.3200 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Remote.Grpc/ReindexerGrpcClient.cs b/src/ReindexerNet.Remote.Grpc/ReindexerGrpcClient.cs index a9c64dd..065042e 100644 --- a/src/ReindexerNet.Remote.Grpc/ReindexerGrpcClient.cs +++ b/src/ReindexerNet.Remote.Grpc/ReindexerGrpcClient.cs @@ -28,6 +28,7 @@ public sealed class ReindexerGrpcClient : IAsyncReindexerClient private readonly GrpcChannel _channel; private readonly ReindexerGrpc.ReindexerClient _grpcClient; private readonly OutputFlags _outputFlags; + private static readonly ReindexerJsonSerializer _defaultJsonSerializer = new(); /// Connection string for the implementation. /// @@ -70,9 +71,11 @@ public ReindexerGrpcClient(ReindexerConnectionString connectionString, IReindexe _connectionString.GrpcAddress = "http://" + _connectionString.GrpcAddress; #if LEGACY_GRPC_CORE var ipEndpoint = new Uri(_connectionString.GrpcAddress, UriKind.Absolute); - var channelOptions = new List(); - channelOptions.Add(new ChannelOption(ChannelOptions.MaxReceiveMessageLength, maxReceiveMessageSize ?? -1)); - channelOptions.Add(new ChannelOption(ChannelOptions.MaxSendMessageLength, maxSendMessageSize ?? -1)); + var channelOptions = new List + { + new ChannelOption(ChannelOptions.MaxReceiveMessageLength, maxReceiveMessageSize ?? -1), + new ChannelOption(ChannelOptions.MaxSendMessageLength, maxSendMessageSize ?? -1) + }; configureChannelOptions?.Invoke(channelOptions); _channel = new GrpcChannel(ipEndpoint.Host, ipEndpoint.Port //doesn't support subdirectories. @@ -227,7 +230,21 @@ public async Task AddIndexAsync(string nsName, Index indexDefinition, Cancellati Name = indexDefinition.Name, FieldType = indexDefinition.FieldType.ToEnumString(), IndexType = indexDefinition.IndexType.ToEnumString(), + +/* Unmerged change from project 'ReindexerNet.Remote.Grpc (net472)' +Before: JsonPaths = { indexDefinition.JsonPaths ?? new List() { indexDefinition.Name } }, +After: + JsonPaths = { indexDefinition.JsonPaths ?? new List() { indexDefinition.Name] }, +*/ + +/* Unmerged change from project 'ReindexerNet.Remote.Grpc (netstandard2.0)' +Before: + JsonPaths = { indexDefinition.JsonPaths ?? new List() { indexDefinition.Name } }, +After: + JsonPaths = { indexDefinition.JsonPaths ?? new List() { indexDefinition.Name] }, +*/ + JsonPaths = { indexDefinition.JsonPaths ?? [indexDefinition.Name] }, Options = new IndexOptions { CollateMode = Enum.TryParse(indexDefinition.CollateMode, true, out IndexOptions.Types.CollateMode collateMode) ? collateMode : IndexOptions.Types.CollateMode.CollateNoneMode, @@ -257,7 +274,7 @@ public async Task UpdateIndexAsync(string nsName, Index indexDefinition, Cancell Name = indexDefinition.Name, FieldType = indexDefinition.FieldType.ToEnumString(), IndexType = indexDefinition.IndexType.ToEnumString(), - JsonPaths = { indexDefinition.JsonPaths ?? new List() { indexDefinition.Name } }, + JsonPaths = { indexDefinition.JsonPaths ?? [indexDefinition.Name] }, Options = new IndexOptions { CollateMode = Enum.TryParse(indexDefinition.CollateMode, true, out IndexOptions.Types.CollateMode collateMode) ? @@ -378,6 +395,60 @@ public async Task DeleteAsync(string nsName, IEnumerable item return await ModifyItemsAsync(nsName, ItemModifyMode.Delete, items, precepts, cancellationToken).ConfigureAwait(false); } + /// + public Task> ExecuteAsync(string @namespace, Action query, CancellationToken cancellationToken = default) + { + using var builder = new SerializableQueryBuilder(_defaultJsonSerializer, @namespace); + query(builder); + var buffer = builder.CloseQuery(); + + return ExecuteAsync(buffer.ToArray(), SerializerType.Json, cancellationToken); + } + + /// + public async Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default) + { + var asyncReq = _grpcClient.Select(new SelectRequest + { + DbName = _connectionString.DatabaseName, + Query = new Reindexer.Grpc.Query{ Data = ByteString.CopyFrom(query), EncdoingType = queryEncoding switch + { + SerializerType.Json => EncodingType.Json, + SerializerType.Msgpack => EncodingType.Msgpack, + SerializerType.Cjson => EncodingType.Cjson, + SerializerType.Protobuf => EncodingType.Protobuf, + _ => throw new NotImplementedException(), + } }, + Flags = _outputFlags + }); + var result = new QueryItemsOf { Items = [] }; + var hasResultOptSet = false; + await foreach (var (queryItems, resultOpt) in asyncReq.HandleResponseAsync(_serializer, cancellationToken)) + { + if (!hasResultOptSet) + { + if (resultOpt != null) + { + result.QueryTotalItems = resultOpt.TotalItems != 0 ? resultOpt.TotalItems : resultOpt.QueryTotalItems; //why? + result.Explain = !string.IsNullOrWhiteSpace(resultOpt.Explain) ? resultOpt.Explain.AsSpan().DeserializeJson() : new ExplainDef(); + result.CacheEnabled = resultOpt.CacheEnabled; + } + + hasResultOptSet = true; + } + if (queryItems.Items?.Count > 0) + result.Items.AddRange(queryItems.Items); + } + + return result; + } + + /// + public Task> ExecuteAsync(byte[] query, SerializerType queryEncoding, CancellationToken cancellationToken = default) + { + return ExecuteAsync(query, queryEncoding, cancellationToken); + } + /// public async Task> ExecuteSqlAsync(string sql, CancellationToken cancellationToken = default) { @@ -387,7 +458,7 @@ public async Task> ExecuteSqlAsync(string sql, Cancel Sql = sql, Flags = _outputFlags }); - var result = new QueryItemsOf { Items = new List() }; + var result = new QueryItemsOf { Items = [] }; var hasResultOptSet = false; await foreach (var (queryItems, resultOpt) in asyncReq.HandleResponseAsync(_serializer, cancellationToken)) { diff --git a/src/ReindexerNet.Remote.Grpc/ReindexerNet.Remote.Grpc.csproj b/src/ReindexerNet.Remote.Grpc/ReindexerNet.Remote.Grpc.csproj index 0e7aedf..7559c36 100644 --- a/src/ReindexerNet.Remote.Grpc/ReindexerNet.Remote.Grpc.csproj +++ b/src/ReindexerNet.Remote.Grpc/ReindexerNet.Remote.Grpc.csproj @@ -7,9 +7,9 @@ ReindexerNet.Remote.Grpc Reindexer Grpc library to connect to a standalone reindexer server. reindexer remote grpc standalone document-db in-memory - 0.3.10.3032 - 0.3.10 - 0.3.10 + 0.4.0.3032 + 0.4.0 + 0.4.0 diff --git a/src/ReindexerNet.Remote.OpenApi/ReindexerNet.Remote.OpenApi.csproj b/src/ReindexerNet.Remote.OpenApi/ReindexerNet.Remote.OpenApi.csproj index a0e2fbf..7f99b3b 100644 --- a/src/ReindexerNet.Remote.OpenApi/ReindexerNet.Remote.OpenApi.csproj +++ b/src/ReindexerNet.Remote.OpenApi/ReindexerNet.Remote.OpenApi.csproj @@ -7,9 +7,9 @@ ReindexerNet.Remote.OpenApi Reindexer open api library to connect to a standalone reindexer server. reindexer openapi embedded document-db in-memory - 0.3.10.3032 - 0.3.10 - 0.3.10 + 0.4.0.3032 + 0.4.0 + 0.4.0 true