From 7e27c311cf01f0969e0b2797a3895c30279a8428 Mon Sep 17 00:00:00 2001 From: David Bottiau Date: Mon, 19 Aug 2024 15:22:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20method=20to=20create=20multip?= =?UTF-8?q?le=20records=20at=20once=20(#117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Internals/SurrealDbEngine.InMemory.cs | 11 + .../NativeMethods.g.cs | 1 + .../SurrealDbMemoryClient.cs | 10 + SurrealDb.Net.Tests/InsertTests.cs | 220 ++++++++++++++++++ .../Internals/SurrealDbEngine.Http.cs | 5 +- SurrealDb.Net/SurrealDbClient.Interface.cs | 15 +- SurrealDb.Net/SurrealDbClient.cs | 2 +- rust-embedded/memory/src/models/method.rs | 3 +- 8 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 SurrealDb.Net.Tests/InsertTests.cs diff --git a/SurrealDb.Embedded.InMemory/Internals/SurrealDbEngine.InMemory.cs b/SurrealDb.Embedded.InMemory/Internals/SurrealDbEngine.InMemory.cs index b44114a6..bcefb0f6 100644 --- a/SurrealDb.Embedded.InMemory/Internals/SurrealDbEngine.InMemory.cs +++ b/SurrealDb.Embedded.InMemory/Internals/SurrealDbEngine.InMemory.cs @@ -214,6 +214,17 @@ public Task Info(CancellationToken cancellationToken) throw new NotSupportedException("Authentication is not enabled in embedded mode."); } + public async Task> Insert( + string table, + IEnumerable data, + CancellationToken cancellationToken + ) + where T : Record + { + return await SendRequestAsync>(Method.Insert, [table, data], cancellationToken) + .ConfigureAwait(false); + } + public Task Invalidate(CancellationToken cancellationToken) { throw new NotSupportedException("Authentication is not enabled in embedded mode."); diff --git a/SurrealDb.Embedded.InMemory/NativeMethods.g.cs b/SurrealDb.Embedded.InMemory/NativeMethods.g.cs index 270a083c..b8d6533c 100644 --- a/SurrealDb.Embedded.InMemory/NativeMethods.g.cs +++ b/SurrealDb.Embedded.InMemory/NativeMethods.g.cs @@ -71,6 +71,7 @@ internal enum Method : byte Set = 3, Unset = 4, Select = 5, + Insert = 6, Create = 7, Update = 8, Merge = 9, diff --git a/SurrealDb.Embedded.InMemory/SurrealDbMemoryClient.cs b/SurrealDb.Embedded.InMemory/SurrealDbMemoryClient.cs index 56c47e9d..88a01428 100644 --- a/SurrealDb.Embedded.InMemory/SurrealDbMemoryClient.cs +++ b/SurrealDb.Embedded.InMemory/SurrealDbMemoryClient.cs @@ -135,6 +135,16 @@ public Task Info(CancellationToken cancellationToken = default) return _engine.Info(cancellationToken); } + public Task> Insert( + string table, + IEnumerable data, + CancellationToken cancellationToken = default + ) + where T : Record + { + return _engine.Insert(table, data, cancellationToken); + } + public Task Invalidate(CancellationToken cancellationToken = default) { return _engine.Invalidate(cancellationToken); diff --git a/SurrealDb.Net.Tests/InsertTests.cs b/SurrealDb.Net.Tests/InsertTests.cs new file mode 100644 index 00000000..bfa3573d --- /dev/null +++ b/SurrealDb.Net.Tests/InsertTests.cs @@ -0,0 +1,220 @@ +using System.Text; + +namespace SurrealDb.Net.Tests; + +public class InsertTests +{ + [Theory] + [InlineData("Endpoint=mem://")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=CBOR")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=CBOR")] + public async Task ShouldInsertZeroRecord(string connectionString) + { + IEnumerable? list = null; + IEnumerable? result = null; + + Func func = async () => + { + await using var surrealDbClientGenerator = new SurrealDbClientGenerator(); + var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo(); + + string filePath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "Schemas/post.surql" + ); + string fileContent = File.ReadAllText(filePath, Encoding.UTF8); + + string query = fileContent; + + using var client = surrealDbClientGenerator.Create(connectionString); + await client.Use(dbInfo.Namespace, dbInfo.Database); + await client.RawQuery(query); + + result = await client.Insert("empty", Enumerable.Empty()); + + list = await client.Select("empty"); + }; + + await func.Should().NotThrowAsync(); + + list.Should().NotBeNull().And.HaveCount(0); + + result.Should().NotBeNull().And.HaveCount(0); + } + + [Theory] + [InlineData("Endpoint=mem://")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=CBOR")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=CBOR")] + public async Task ShouldInsertPostsWithAutogeneratedId(string connectionString) + { + IEnumerable? list = null; + IEnumerable? result = null; + + Func func = async () => + { + await using var surrealDbClientGenerator = new SurrealDbClientGenerator(); + var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo(); + + string filePath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "Schemas/post.surql" + ); + string fileContent = File.ReadAllText(filePath, Encoding.UTF8); + + string query = fileContent; + + using var client = surrealDbClientGenerator.Create(connectionString); + await client.Use(dbInfo.Namespace, dbInfo.Database); + await client.RawQuery(query); + + var post = new Post + { + Title = "A new article", + Content = "This is a new article created using the .NET SDK" + }; + + result = await client.Insert("post", new[] { post }); + + list = await client.Select("post"); + }; + + await func.Should().NotThrowAsync(); + + list.Should().NotBeNull().And.HaveCount(3); + + result.Should().NotBeNull().And.HaveCount(1); + + var post = result!.First(); + post!.Title.Should().Be("A new article"); + post!.Content.Should().Be("This is a new article created using the .NET SDK"); + post!.CreatedAt.Should().NotBeNull(); + post!.Status.Should().Be("DRAFT"); + } + + [Theory] + [InlineData("Endpoint=mem://")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=CBOR")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=CBOR")] + public async Task ShouldInsertPostsWithPredefinedId(string connectionString) + { + IEnumerable? list = null; + IEnumerable? result = null; + + Func func = async () => + { + await using var surrealDbClientGenerator = new SurrealDbClientGenerator(); + var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo(); + + string filePath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "Schemas/post.surql" + ); + string fileContent = File.ReadAllText(filePath, Encoding.UTF8); + + string query = fileContent; + + using var client = surrealDbClientGenerator.Create(connectionString); + await client.Use(dbInfo.Namespace, dbInfo.Database); + await client.RawQuery(query); + + var post = new Post + { + Id = new Thing("post", "another"), + Title = "A new article", + Content = "This is a new article created using the .NET SDK" + }; + + result = await client.Insert("post", new[] { post }); + + list = await client.Select("post"); + }; + + await func.Should().NotThrowAsync(); + + list.Should().NotBeNull().And.HaveCount(3); + + result.Should().NotBeNull().And.HaveCount(1); + + var post = result!.First(); + post!.Title.Should().Be("A new article"); + post!.Content.Should().Be("This is a new article created using the .NET SDK"); + post!.CreatedAt.Should().NotBeNull(); + post!.Status.Should().Be("DRAFT"); + + var anotherPost = list!.First(r => r.Id!.Id == "another"); + + anotherPost.Should().NotBeNull(); + anotherPost!.Title.Should().Be("A new article"); + anotherPost!.Content.Should().Be("This is a new article created using the .NET SDK"); + anotherPost!.CreatedAt.Should().NotBeNull(); + anotherPost!.Status.Should().Be("DRAFT"); + } + + [Theory] + [InlineData("Endpoint=mem://")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=http://127.0.0.1:8000;User=root;Pass=root;Serialization=CBOR")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=JSON")] + [InlineData("Endpoint=ws://127.0.0.1:8000/rpc;User=root;Pass=root;Serialization=CBOR")] + public async Task ShouldInsertMultiplePosts(string connectionString) + { + IEnumerable? result = null; + IEnumerable? list = null; + + Func func = async () => + { + await using var surrealDbClientGenerator = new SurrealDbClientGenerator(); + var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo(); + + string filePath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, + "Schemas/post.surql" + ); + string fileContent = File.ReadAllText(filePath, Encoding.UTF8); + + string query = fileContent; + + using var client = surrealDbClientGenerator.Create(connectionString); + await client.Use(dbInfo.Namespace, dbInfo.Database); + await client.RawQuery(query); + + var posts = new List + { + new Post + { + Id = new Thing("post", "A"), + Title = "An article", + Content = "This is a new article" + }, + new Post + { + Id = new Thing("post", "B"), + Title = "An article", + Content = "This is a new article" + }, + new Post + { + Id = new Thing("post", "C"), + Title = "An article", + Content = "This is a new article" + } + }; + + result = await client.Insert("post", posts); + + list = await client.Select("post"); + }; + + await func.Should().NotThrowAsync(); + + result.Should().NotBeNull().And.HaveCount(3); + list.Should().NotBeNull().And.HaveCount(5); + } +} diff --git a/SurrealDb.Net/Internals/SurrealDbEngine.Http.cs b/SurrealDb.Net/Internals/SurrealDbEngine.Http.cs index 64c912f9..d94c6be0 100644 --- a/SurrealDb.Net/Internals/SurrealDbEngine.Http.cs +++ b/SurrealDb.Net/Internals/SurrealDbEngine.Http.cs @@ -267,13 +267,12 @@ CancellationToken cancellationToken ) where T : IRecord { - object?[] @params = [data]; - var request = new SurrealDbHttpRequest { Method = "insert", Parameters = @params }; + var request = new SurrealDbHttpRequest { Method = "insert", Parameters = [table, data] }; var dbResponse = await ExecuteRequestAsync(request, cancellationToken) .ConfigureAwait(false); - return dbResponse.GetValue>()!; + return dbResponse.DeserializeEnumerable(); } public Task Invalidate(CancellationToken _) diff --git a/SurrealDb.Net/SurrealDbClient.Interface.cs b/SurrealDb.Net/SurrealDbClient.Interface.cs index 953dabbc..8b962bf5 100644 --- a/SurrealDb.Net/SurrealDbClient.Interface.cs +++ b/SurrealDb.Net/SurrealDbClient.Interface.cs @@ -70,6 +70,9 @@ public interface ISurrealDbClient : IDisposable /// /// Creates the specific record in the database. /// + /// + /// Note: This method creates only a single record. If the record already exist, it will throw an error. + /// /// The type of the record to create. /// The record to create. /// The cancellationToken enables graceful cancellation of asynchronous operations @@ -174,11 +177,15 @@ Task Create( Task Info(CancellationToken cancellationToken = default); /// - /// Inserts a set of records in a table in the database. + /// Inserts a collection of records in the database. /// - /// The type of the records to insert. - /// The table name where the record will be stored. - /// The records to insert. + /// + /// Note: This method allows you to create multiple records at once. + /// In case a record already exist, it will not throw error and it will not update the existing record. + /// + /// The type of the record to create. + /// The table name where the records will be stored. + /// The records to create. /// The cancellationToken enables graceful cancellation of asynchronous operations /// The records created. /// diff --git a/SurrealDb.Net/SurrealDbClient.cs b/SurrealDb.Net/SurrealDbClient.cs index 0df1bbe6..6cb248ce 100644 --- a/SurrealDb.Net/SurrealDbClient.cs +++ b/SurrealDb.Net/SurrealDbClient.cs @@ -254,7 +254,7 @@ public Task> Insert( ) where T : IRecord { - return _engine.Insert(table, data, cancellationToken); + return _engine.Insert(table, data, cancellationToken); } public Task Invalidate(CancellationToken cancellationToken = default) diff --git a/rust-embedded/memory/src/models/method.rs b/rust-embedded/memory/src/models/method.rs index d4c43356..d00b6c75 100644 --- a/rust-embedded/memory/src/models/method.rs +++ b/rust-embedded/memory/src/models/method.rs @@ -5,7 +5,7 @@ pub enum Method { Set = 3, Unset = 4, Select = 5, - //Insert = 6, // TODO + Insert = 6, Create = 7, Update = 8, Merge = 9, @@ -24,6 +24,7 @@ impl From for surrealdb::rpc::method::Method { Method::Set => surrealdb::rpc::method::Method::Set, Method::Unset => surrealdb::rpc::method::Method::Unset, Method::Select => surrealdb::rpc::method::Method::Select, + Method::Insert => surrealdb::rpc::method::Method::Insert, Method::Create => surrealdb::rpc::method::Method::Create, Method::Update => surrealdb::rpc::method::Method::Update, Method::Merge => surrealdb::rpc::method::Method::Merge,