Skip to content
This repository has been archived by the owner on Dec 18, 2023. It is now read-only.

Added tests for additional db operations #67

Merged
merged 6 commits into from
Sep 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: Build & Test

on:
workflow_dispatch:
pull_request:
push:
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
Expand Down
8 changes: 4 additions & 4 deletions src/Abstractions/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ public interface IDatabase<TResponse>
/// Applies <see href="https://jsonpatch.com/"> JSON Patch </see> changes to all records, or a specific record, in the database.
/// </summary>
/// <param name="thing"> The table name or the specific record id to update. </param>
/// <param name="data"> The JSON Patch data with which to modify the records. </param>
/// <param name="patches"> The JSON Patch data with which to modify the records. </param>
/// <remarks>
/// This function patches the current document / record data with the specified JSON Patch data.
/// This function will run the following query in the database:
/// <code>UPDATE $thing PATCH $data;</code>
/// </remarks>
public new Task<TResponse> Modify(Thing thing, object data, CancellationToken ct = default);
public new Task<TResponse> Modify(Thing thing, object[] patches, CancellationToken ct = default);

/// <summary>
/// Deletes all records in a table, or a specific record, from the database.
Expand Down Expand Up @@ -222,7 +222,7 @@ public interface IDatabase {
/// <param name="sql"> Specifies the SurrealQL statements. </param>
/// <param name="vars"> Assigns variables which can be used in the query. </param>
/// <param name="ct"> </param>
public Task<IResponse> Query(string sql, IReadOnlyDictionary<string, object?>? vars, CancellationToken ct = default);
public Task<IResponse> Query(string sql, IReadOnlyDictionary<string, object?>? vars = null, CancellationToken ct = default);

/// <summary>
/// Selects all records in a table, or a specific record, from the database.
Expand Down Expand Up @@ -279,7 +279,7 @@ public interface IDatabase {
/// This function will run the following query in the database:
/// <code>UPDATE $thing PATCH $data;</code>
/// </remarks>
public Task<IResponse> Modify(Thing thing, object data, CancellationToken ct = default);
public Task<IResponse> Modify(Thing thing, object[] data, CancellationToken ct = default);

/// <summary>
/// Deletes all records in a table, or a specific record, from the database.
Expand Down
4 changes: 2 additions & 2 deletions src/Driver/Rest/DatabaseRest.IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ async Task<IResponse> IDatabase.Change(Thing thing, object data, CancellationTok
return await Change(thing, data, ct);
}

async Task<IResponse> IDatabase.Modify(Thing thing, object data, CancellationToken ct) {
return await Modify(thing, data, ct);
async Task<IResponse> IDatabase.Modify(Thing thing, object[] patches, CancellationToken ct) {
return await Modify(thing, patches, ct);
}

async Task<IResponse> IDatabase.Delete(Thing thing, CancellationToken ct) {
Expand Down
24 changes: 9 additions & 15 deletions src/Driver/Rest/DatabaseRest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public Task<RestResponse> Let(
if (value is null) {
_vars.Remove(key);
} else {
_vars[key] = ToJson(value);
_vars[key] = value;
}

return CompletedOk;
Expand Down Expand Up @@ -151,25 +151,19 @@ public async Task<RestResponse> Update(
return await Update(thing, ToJsonContent(data), ct);
}

public async Task<RestResponse> Change(
Thing thing,
object data,
CancellationToken ct = default) {
// Is this the most optimal way?
string sql = "UPDATE $what MERGE $data RETURN AFTER";
Dictionary<string, object?> vars = new() { ["what"] = thing, ["data"] = data, };
return await Query(sql, vars, ct);
}

public async Task<RestResponse> Modify(
Thing thing,
object data,
CancellationToken ct = default) {
public async Task<RestResponse> Change(Thing thing, object data, CancellationToken ct = default) {
HttpRequestMessage req = ToRequestMessage(HttpMethod.Patch, BuildRequestUri(thing), ToJson(data));
HttpResponseMessage rsp = await _client.SendAsync(req, ct);
return await rsp.ToSurreal();
}

public async Task<RestResponse> Modify(Thing thing, object[] patches, CancellationToken ct = default) {
// Is this the most optimal way?
string sql = "UPDATE $what PATCH $data RETURN DIFF";
Dictionary<string, object?> vars = new() { ["what"] = thing, ["data"] = patches, };
return await Query(sql, vars, ct);
}

public async Task<RestResponse> Delete(
Thing thing,
CancellationToken ct = default) {
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/Rest/RestResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SurrealDB.Driver.Rest;
/// <summary>
/// The response from a query to the Surreal database via rest.
/// </summary>
[DebuggerDisplay("{ToString},nq")]
[DebuggerDisplay("{ToString,nq}")]
public readonly struct RestResponse : IResponse {
private readonly string? _status;
private readonly string? _detail;
Expand Down
4 changes: 2 additions & 2 deletions src/Driver/Rpc/DatabaseRpc.IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ async Task<IResponse> IDatabase.Change(Thing thing, object data, CancellationTok
return await Change(thing, data, ct);
}

async Task<IResponse> IDatabase.Modify(Thing thing, object data, CancellationToken ct) {
return await Modify(thing, data, ct);
async Task<IResponse> IDatabase.Modify(Thing thing, object[] patches, CancellationToken ct) {
return await Modify(thing, patches, ct);
}

async Task<IResponse> IDatabase.Delete(Thing thing, CancellationToken ct) {
Expand Down
4 changes: 2 additions & 2 deletions src/Driver/Rpc/DatabaseRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ public async Task<RpcResponse> Change(
/// <inheritdoc />
public async Task<RpcResponse> Modify(
Thing thing,
object data,
object[] patches,
CancellationToken ct = default) {
return await _client.Send(new() { method = "modify", parameters = new() { thing, data, }, }, ct).ToSurreal();
return await _client.Send(new() { method = "modify", parameters = new() { thing, patches, }, }, ct).ToSurreal();
}

/// <inheritdoc />
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/Rpc/RpcResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SurrealDB.Driver.Rpc;
/// <summary>
/// The response from a query to the Surreal database via rpc.
/// </summary>
[DebuggerDisplay("{ToString()},nq")]
[DebuggerDisplay("{ToString(),nq}")]
public readonly struct RpcResponse : IResponse {
private readonly Error _error;

Expand Down
2 changes: 1 addition & 1 deletion src/Models/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ namespace SurrealDB.Models;
/// <summary>
/// The result of a failed query to the Surreal database.
/// </summary>
[DebuggerDisplay("{Code},nq: {Message},nq")]
[DebuggerDisplay("{Code,nq}: {Message,nq}")]
public readonly record struct Error(int Code, string? Message);
2 changes: 1 addition & 1 deletion src/Models/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace SurrealDB.Models;
/// <summary>
/// The result of a successful query to the Surreal database.
/// </summary>
[DebuggerDisplay("{Inner},nq")]
[DebuggerDisplay("{Inner,nq}")]
public readonly struct Result : IEquatable<Result>, IComparable<Result> {
private readonly JsonElement _json;
private readonly object? _sentinelOrValue;
Expand Down
2 changes: 1 addition & 1 deletion src/Models/Thing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace SurrealDB.Models;
/// `table_name:record_id`
/// </remarks>
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Inner},nq")]
[DebuggerDisplay("{Inner,nq}")]
public readonly record struct Thing {
public const char CHAR_SEP = ':';
public const char CHAR_PRE = '⟨';
Expand Down
4 changes: 4 additions & 0 deletions src/Ws/Ws.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ private async Task Receive(CancellationToken stoppingToken) {

stoppingToken.ThrowIfCancellationRequested();

if (String.IsNullOrEmpty(id)) {
continue; // Invalid response
}

if (!_handlers.TryGetValue(id, out IHandler? handler)) {
// assume that unhandled responses belong to other clients
// discard!
Expand Down
33 changes: 26 additions & 7 deletions src/Ws/WsTx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ public void Dispose() {
/// Parses the header.
/// The body contains the result array including the end object token `[...]}`.
/// </summary>
public async Task<(string id, RspHeader rsp, NtyHeader nty, Stream body)> Tr(CancellationToken ct) {
public async Task<(string? id, RspHeader rsp, NtyHeader nty, Stream body)> Tr(CancellationToken ct) {
ThrowIfDisconnected();
// this method assumes that the header size never exceeds DefaultBufferSize!
IMemoryOwner<byte> owner = MemoryPool<byte>.Shared.Rent(DefaultBufferSize);
var r = await _ws.ReceiveAsync(owner.Memory, ct);

if (r.Count <= 0) {
return (default, default, default, default!);
}

// parse the header
var (rsp, nty, off) = ParseHeader(owner, r);
var (rsp, nty, off) = ParseHeader(owner.Memory.Span.Slice(0, r.Count));
string? id = rsp.IsDefault ? nty.id : rsp.id;
if (String.IsNullOrEmpty(id)) {
ThrowHeaderId();
Expand All @@ -57,8 +61,7 @@ public void Dispose() {
return (id, rsp, nty, body);
}

private static (RspHeader rsp, NtyHeader nty, int off) ParseHeader(IMemoryOwner<byte> owner, ValueWebSocketReceiveResult r) {
ReadOnlySpan<byte> utf8 = owner.Memory.Span.Slice(0, r.Count);
private static (RspHeader rsp, NtyHeader nty, int off) ParseHeader(ReadOnlySpan<byte> utf8) {
var (rsp, rspOff, rspErr) = RspHeader.Parse(utf8);
if (rspErr is null) {
return (rsp, default, (int)rspOff);
Expand Down Expand Up @@ -137,7 +140,7 @@ private static void ThrowParseHead(string err, long off) {
throw new JsonException(err, default, default, off);
}

public readonly record struct NtyHeader(string? id, string? method) {
public readonly record struct NtyHeader(string? id, string? method, WsClient.Error err) {
public bool IsDefault => default == this;

/// <summary>
Expand All @@ -153,14 +156,15 @@ internal static (NtyHeader head, long off, string? err) Parse(in ReadOnlySpan<by
if (!fsm.Success) {
return (default, fsm.Lexer.BytesConsumed, $"Error while parsing {nameof(RspHeader)} at {fsm.Lexer.TokenStartIndex}: {fsm.Err}");
}
return (new(fsm.Id, fsm.Method), default, default);
return (new(fsm.Id, fsm.Method, fsm.Error), default, default);
}

private enum Fsms {
Start, // -> Prop
Prop, // -> PropId | PropAsync | PropMethod | ProsResult
PropId, // -> Prop | End
PropMethod, // -> Prop | End
PropError, // -> End
PropParams, // -> End
End
}
Expand All @@ -173,6 +177,7 @@ private ref struct Fsm {

public string? Name;
public string? Id;
public WsClient.Error Error;
public string? Method;

public bool MoveNext() {
Expand All @@ -181,6 +186,7 @@ public bool MoveNext() {
Fsms.Prop => Prop(),
Fsms.PropId => PropId(),
Fsms.PropMethod => PropMethod(),
Fsms.PropError => PropError(),
Fsms.PropParams => PropParams(),
Fsms.End => End(),
_ => false
Expand Down Expand Up @@ -218,6 +224,10 @@ private bool Prop() {
State = Fsms.PropMethod;
return true;
}
if ("error".Equals(Name, StringComparison.OrdinalIgnoreCase)) {
State = Fsms.PropError;
return true;
}
if ("params".Equals(Name, StringComparison.OrdinalIgnoreCase)) {
State = Fsms.PropParams;
return true;
Expand All @@ -238,6 +248,12 @@ private bool PropId() {
return true;
}

private bool PropError() {
Error = JsonSerializer.Deserialize<WsClient.Error>(ref Lexer, SerializerOptions.Shared);
State = Fsms.End;
return true;
}

private bool PropMethod() {
if (!Lexer.Read() || Lexer.TokenType != JsonTokenType.String) {
Err = "Unable to read `method` property value";
Expand Down Expand Up @@ -340,7 +356,10 @@ private bool Prop() {
State = Fsms.PropResult;
return true;
}

if ("error".Equals(Name, StringComparison.OrdinalIgnoreCase)) {
State = Fsms.PropError;
return true;
}

Err = $"Unknown PropertyName `{Name}`";
return false;
Expand Down
1 change: 0 additions & 1 deletion tests/Driver.Tests/DatabaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ await db.Change(
public abstract class DriverBase<T>
where T : IDatabase, IDisposable, new() {

TestDatabaseFixture? fixture;

[Fact]
public async Task TestSuite() {
Expand Down
4 changes: 2 additions & 2 deletions tests/Driver.Tests/Queries/EqualityQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public abstract class EqualityQueryTests<T, TKey, TValue> : QueryTests<T, TKey,
where T : IDatabase, IDisposable, new() {

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task EqualsQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! == (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand All @@ -23,7 +23,7 @@ public async Task EqualsQueryTest(TValue val1, TValue val2) => await DbHandle<T>
);

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task NotEqualsQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! != (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand Down
7 changes: 4 additions & 3 deletions tests/Driver.Tests/Queries/GeneralQueryTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using SurrealDB.Common;
// ReSharper disable All
#pragma warning disable CS0169

namespace SurrealDB.Driver.Tests.Queries;
public class RestGeneralQueryTests : GeneralQueryTests<DatabaseRest> { }
Expand All @@ -8,7 +10,6 @@ public class RpcGeneralQueryTests : GeneralQueryTests<DatabaseRpc> { }
public abstract class GeneralQueryTests<T>
where T : IDatabase, IDisposable, new() {

TestDatabaseFixture? fixture;

private record GroupedCountries {
string? country;
Expand Down Expand Up @@ -117,7 +118,7 @@ public async Task EpsilonAdditionQueryTest() => await DbHandle<T>.WithDatabase(
Assert.True(response.TryGetResult(out Result result));
MathResultDocument? doc = result.GetObject<MathResultDocument>();
Assert.NotNull(doc);
doc.result.Should().BeApproximately(expectedResult.result, 0.000001f);
doc!.result.Should().BeApproximately(expectedResult.result, 0.000001f);
}
);

Expand All @@ -139,7 +140,7 @@ public async Task MinValueAdditionQueryTest() => await DbHandle<T>.WithDatabase(
Assert.True(response.TryGetResult(out Result result));
MathResultDocument? doc = result.GetObject<MathResultDocument>();
Assert.NotNull(doc);
doc.result.Should().BeApproximately(expectedResult.result, 0.001f);
doc!.result.Should().BeApproximately(expectedResult.result, 0.001f);
}
);

Expand Down
8 changes: 4 additions & 4 deletions tests/Driver.Tests/Queries/InequalityQueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public abstract class InequalityQueryTests<T, TKey, TValue> : EqualityQueryTests
where T : IDatabase, IDisposable, new() {

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task LessThanQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! < (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand All @@ -23,7 +23,7 @@ public async Task LessThanQueryTest(TValue val1, TValue val2) => await DbHandle<
);

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task LessThanOrEqualToQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! <= (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand All @@ -42,7 +42,7 @@ public async Task LessThanOrEqualToQueryTest(TValue val1, TValue val2) => await
);

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task GreaterThanQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! > (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand All @@ -61,7 +61,7 @@ public async Task GreaterThanQueryTest(TValue val1, TValue val2) => await DbHand
);

[Theory]
[MemberData("KeyPairs")]
[MemberData("ValuePairs")]
public async Task GreaterThanOrEqualToQueryTest(TValue val1, TValue val2) => await DbHandle<T>.WithDatabase(
async db => {
var expectedResult = (dynamic)val1! >= (dynamic)val2!; // Can't do operator overloads on generic types, so force it by casting to a dynamic
Expand Down
Loading