Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ implement Import method #174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions SurrealDb.Embedded.InMemory/NativeMethods.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ internal static unsafe partial class NativeMethods
[DllImport(__DllName, EntryPoint = "execute", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void execute(int id, Method method, byte* bytes, int len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
/// Executes the "import" method of a SurrealDB engine (given its id).
/// </summary>
[DllImport(__DllName, EntryPoint = "import", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void import(int id, ushort* utf16_str, int utf16_len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
Expand Down
77 changes: 77 additions & 0 deletions SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,83 @@
}
}

public async Task Import(string input, CancellationToken cancellationToken)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
cancellationToken.Register(timeoutCts.Cancel);

var taskCompletionSource = new TaskCompletionSource<Unit>();
timeoutCts.Token.Register(() =>
{
taskCompletionSource.TrySetCanceled();
});

Check warning on line 349 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L347-L349

Added lines #L347 - L349 were not covered by tests

Action<ByteBuffer> success = (_) =>
{
try
{
taskCompletionSource.SetResult(default);
}
catch (Exception e)
{
taskCompletionSource.SetException(e);
}

Check warning on line 360 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L357-L360

Added lines #L357 - L360 were not covered by tests
};
Action<ByteBuffer> fail = (byteBuffer) =>
{
string error = CborSerializer.Deserialize<string>(
byteBuffer.AsReadOnly(),
GetCborOptions()
);
taskCompletionSource.SetException(new SurrealDbException(error));
};

Check warning on line 369 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L363-L369

Added lines #L363 - L369 were not covered by tests

var successHandle = GCHandle.Alloc(success);
var failureHandle = GCHandle.Alloc(fail);

unsafe
{
var successAction = new SuccessAction()
{
handle = new RustGCHandle()
{
ptr = GCHandle.ToIntPtr(successHandle),
drop_callback = &NativeBindings.DropGcHandle,
},
callback = &NativeBindings.SuccessCallback,
};

var failureAction = new FailureAction()
{
handle = new RustGCHandle()
{
ptr = GCHandle.ToIntPtr(failureHandle),
drop_callback = &NativeBindings.DropGcHandle,
},
callback = &NativeBindings.FailureCallback,
};

fixed (char* p = input.AsSpan())
{
NativeMethods.import(_id, (ushort*)p, input.Length, successAction, failureAction);
}
}

try
{
await taskCompletionSource.Task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{

Check warning on line 407 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L406-L407

Added lines #L406 - L407 were not covered by tests
if (!cancellationToken.IsCancellationRequested)
{
throw new TimeoutException();

Check warning on line 410 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L409-L410

Added lines #L409 - L410 were not covered by tests
}

throw;

Check warning on line 413 in SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Embedded.Internals/SurrealDbEmbeddedEngine.cs#L413

Added line #L413 was not covered by tests
}
}

public Task<T> Info<T>(CancellationToken cancellationToken)
{
throw new NotSupportedException("Authentication is not enabled in embedded mode.");
Expand Down
8 changes: 8 additions & 0 deletions SurrealDb.Embedded.RocksDb/NativeMethods.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ internal static unsafe partial class NativeMethods
[DllImport(__DllName, EntryPoint = "execute", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void execute(int id, Method method, byte* bytes, int len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
/// Executes the "import" method of a SurrealDB engine (given its id).
/// </summary>
[DllImport(__DllName, EntryPoint = "import", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void import(int id, ushort* utf16_str, int utf16_len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
Expand Down
8 changes: 8 additions & 0 deletions SurrealDb.Embedded.SurrealKv/NativeMethods.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ internal static unsafe partial class NativeMethods
[DllImport(__DllName, EntryPoint = "execute", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void execute(int id, Method method, byte* bytes, int len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
/// Executes the "import" method of a SurrealDB engine (given its id).
/// </summary>
[DllImport(__DllName, EntryPoint = "import", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
internal static extern void import(int id, ushort* utf16_str, int utf16_len, SuccessAction success, FailureAction failure);

/// <summary>
/// # Safety
///
Expand Down
50 changes: 50 additions & 0 deletions SurrealDb.Net.Tests/ImportTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
namespace SurrealDb.Net.Tests;

public class ImportTests
{
private const string IMPORT_QUERY = """
DEFINE TABLE foo SCHEMALESS;
DEFINE TABLE bar SCHEMALESS;
CREATE foo:1 CONTENT { hello: "world" };
CREATE bar:1 CONTENT { hello: "world" };
DEFINE FUNCTION fn::foo() {
RETURN "bar";
};
""";

[Test]
[ConnectionStringFixtureGenerator]
public async Task ShouldImportDataSuccessfully(string connectionString)
{
var version = await SurrealDbClientGenerator.GetSurrealTestVersion(connectionString);
if (version?.Major < 2)
{
return;
}

await using var surrealDbClientGenerator = new SurrealDbClientGenerator();
var dbInfo = surrealDbClientGenerator.GenerateDatabaseInfo();

var client = surrealDbClientGenerator.Create(connectionString);
await client.Use(dbInfo.Namespace, dbInfo.Database);

Func<Task> func = async () =>
{
await client.Import(IMPORT_QUERY);
};

await func.Should().NotThrowAsync();

// Check imported query by querying the db
var fooRecords = await client.Select<object>("foo");
fooRecords.Should().NotBeNull().And.HaveCount(1);

var barRecords = await client.Select<object>("bar");
barRecords.Should().NotBeNull().And.HaveCount(1);

var fnResult = await client.Run<string>("fn::foo");
fnResult.Should().Be("bar");

await client.DisposeAsync();
}
}
16 changes: 16 additions & 0 deletions SurrealDb.Net/Internals/Http/CommonHttpWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Dahomey.Cbor;
using Semver;

namespace SurrealDb.Net.Internals.Http;

internal sealed record CommonHttpWrapper(
HttpClient HttpClient,
SemVersion? Version,
Action<CborOptions>? ConfigureCborOptions
) : IDisposable
{
public void Dispose()
{
HttpClient.Dispose();
}
}
10 changes: 10 additions & 0 deletions SurrealDb.Net/Internals/SurrealDbEngine.Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ void Initialize(
/// <param name="cancellationToken">The cancellationToken enables graceful cancellation of asynchronous operations</param>
/// <returns>SurrealQL script as <see cref="String"/></returns>
Task<string> Export(ExportOptions? options, CancellationToken cancellationToken);

/// <summary>
/// This method imports data into a SurrealDB database.
/// </summary>
/// <remarks>
/// This method is only supported by SurrealDB v2.0.0 or higher.
/// </remarks>
/// <param name="input"></param>
/// <param name="cancellationToken">The cancellationToken enables graceful cancellation of asynchronous operations</param>
Task Import(string input, CancellationToken cancellationToken = default);
}

public interface ISurrealDbInMemoryEngine : ISurrealDbProviderEngine { }
Expand Down
57 changes: 57 additions & 0 deletions SurrealDb.Net/SurrealDbClient.Base.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using Dahomey.Cbor;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Semver;
using SurrealDb.Net.Exceptions;
using SurrealDb.Net.Extensions.DependencyInjection;
using SurrealDb.Net.Internals;
using SurrealDb.Net.Internals.Auth;
using SurrealDb.Net.Internals.Http;

namespace SurrealDb.Net;

Expand All @@ -26,4 +30,57 @@
loggerFactory is not null ? new SurrealDbLoggerFactory(loggerFactory) : null
);
}

private async Task<CommonHttpWrapper> CreateCommonHttpWrapperAsync(
CancellationToken cancellationToken
)
{
SemVersion? version;
string? ns;
string? db;
IAuth? auth;
Action<CborOptions>? configureCborOptions;

switch (_engine)
{
case SurrealDbHttpEngine httpEngine:
// 💡 Ensures underlying engine is started to retrieve some information
await httpEngine.Connect(cancellationToken).ConfigureAwait(false);

version = httpEngine._version;
ns = httpEngine._config.Ns;
db = httpEngine._config.Db;
auth = httpEngine._config.Auth;
configureCborOptions = httpEngine._configureCborOptions;
break;
case SurrealDbWsEngine wsEngine:
// 💡 Ensures underlying engine is started to retrieve some information
await wsEngine.InternalConnectAsync(true, cancellationToken).ConfigureAwait(false);

version = wsEngine._version;
ns = wsEngine._config.Ns;
db = wsEngine._config.Db;
auth = wsEngine._config.Auth;
configureCborOptions = wsEngine._configureCborOptions;
break;
default:
throw new SurrealDbException("No underlying engine is started.");

Check warning on line 67 in SurrealDb.Net/SurrealDbClient.Base.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Net/SurrealDbClient.Base.cs#L67

Added line #L67 was not covered by tests
}

if (string.IsNullOrWhiteSpace(ns))
{
throw new SurrealDbException("Namespace should be provided to export data.");

Check warning on line 72 in SurrealDb.Net/SurrealDbClient.Base.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Net/SurrealDbClient.Base.cs#L71-L72

Added lines #L71 - L72 were not covered by tests
}
if (string.IsNullOrWhiteSpace(db))
{
throw new SurrealDbException("Database should be provided to export data.");

Check warning on line 76 in SurrealDb.Net/SurrealDbClient.Base.cs

View check run for this annotation

Codecov / codecov/patch

SurrealDb.Net/SurrealDbClient.Base.cs#L75-L76

Added lines #L75 - L76 were not covered by tests
}

var httpClient = new HttpClient();

SurrealDbHttpEngine.SetNsDbHttpClientHeaders(httpClient, version, ns, db);
SurrealDbHttpEngine.SetAuthHttpClientHeaders(httpClient, auth);

return new CommonHttpWrapper(httpClient, version, configureCborOptions);
}
}
10 changes: 10 additions & 0 deletions SurrealDb.Net/SurrealDbClient.Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ Task<string> Export(
/// <exception cref="SurrealDbException"></exception>
Task<bool> Health(CancellationToken cancellationToken = default);

/// <summary>
/// This method imports data into a SurrealDB database.
/// </summary>
/// <remarks>
/// This method is only supported by SurrealDB v2.0.0 or higher.
/// </remarks>
/// <param name="input"></param>
/// <param name="cancellationToken">The cancellationToken enables graceful cancellation of asynchronous operations</param>
Task Import(string input, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves information about the authenticated scope user.
/// </summary>
Expand Down
Loading
Loading