From 39aadf3dd710d4770bdc31fa10c29e2323468ccc Mon Sep 17 00:00:00 2001 From: Tom van Dinther <39470469+tvandinther@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:52:02 +0100 Subject: [PATCH] Refactor prepared statement into new class --- Libsql.Client/DatabaseWrapper.cs | 87 ++++-------------------------- Libsql.Client/Statement.cs | 92 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 Libsql.Client/Statement.cs diff --git a/Libsql.Client/DatabaseWrapper.cs b/Libsql.Client/DatabaseWrapper.cs index 7111a86..edb8edc 100644 --- a/Libsql.Client/DatabaseWrapper.cs +++ b/Libsql.Client/DatabaseWrapper.cs @@ -137,98 +137,29 @@ public async Task Execute(string sql) { return await Task.Run(() => { - unsafe - { - var error = new Error(); - int exitCode; - - fixed (byte* sqlPtr = Encoding.UTF8.GetBytes(sql)) - { - var statement = new libsql_stmt_t(); - exitCode = Bindings.libsql_prepare(sqlPtr, &statement, &error.Ptr); - error.ThrowIfNonZero(exitCode, $"Failed to prepare statement for query: {sql}"); - return ExecuteStatement(statement); - } - } + var statement = new Statement(sql); + return ExecuteStatement(statement); }); } public async Task Execute(string sql, params object[] args) { return await Task.Run(() => { - unsafe { - var statement = new libsql_stmt_t(); - var error = new Error(); - int exitCode; - - fixed (byte* sqlPtr = Encoding.UTF8.GetBytes(sql)) - { - exitCode = Bindings.libsql_prepare(sqlPtr, &statement, &error.Ptr); - } - - error.ThrowIfNonZero(exitCode, $"Failed to prepare statement for query: {sql}"); - - if (args is null) - { - exitCode = Bindings.libsql_bind_null(statement, 1, &error.Ptr); - error.ThrowIfNonZero(exitCode, "Failed to bind null parameter"); - } - else { - for (var i = 0; i < args.Length; i++) - { - var arg = args[i]; - - if (arg is null) - { - exitCode = Bindings.libsql_bind_null(statement, i + 1, &error.Ptr); - error.ThrowIfNonZero(exitCode, "Failed to bind null parameter"); - continue; - } - - switch (arg) - { - case int val: - exitCode = Bindings.libsql_bind_int(statement, i + 1, val, &error.Ptr); - break; - case double d: - exitCode = Bindings.libsql_bind_float(statement, i + 1, d, &error.Ptr); - break; - case string s: - fixed (byte* sPtr = Encoding.UTF8.GetBytes(s)) - { - exitCode = Bindings.libsql_bind_string(statement, i + 1, sPtr, &error.Ptr); - } - break; - case byte[] b: - fixed (byte* bPtr = b) - { - exitCode = Bindings.libsql_bind_blob(statement, i + 1, bPtr, b.Length, &error.Ptr); - } - break; - case null: - exitCode = Bindings.libsql_bind_null(statement, i + 1, &error.Ptr); - break; - default: - throw new ArgumentException($"Unsupported argument type: {arg.GetType()}"); - } - - error.ThrowIfNonZero(exitCode, $"Failed to bind parameter. Type: {arg.GetType()} Value: {arg}"); - } - } - - return ExecuteStatement(statement); - } + var statement = new Statement(sql); + statement.Bind(args); + + return ExecuteStatement(statement); }); } - private unsafe IResultSet ExecuteStatement(libsql_stmt_t statement) + private unsafe IResultSet ExecuteStatement(Statement statement) { var error = new Error(); var rows = new libsql_rows_t(); int exitCode; - exitCode = Bindings.libsql_execute_stmt(_connection, statement, &rows, &error.Ptr); - Bindings.libsql_free_stmt(statement); + exitCode = Bindings.libsql_execute_stmt(_connection, statement.Stmt, &rows, &error.Ptr); + statement.Dispose(); error.ThrowIfNonZero(exitCode, "Failed to execute statement"); diff --git a/Libsql.Client/Statement.cs b/Libsql.Client/Statement.cs new file mode 100644 index 0000000..2ff1bc2 --- /dev/null +++ b/Libsql.Client/Statement.cs @@ -0,0 +1,92 @@ +using System; +using System.Text; + +namespace Libsql.Client +{ + internal class Statement + { + public libsql_stmt_t Stmt; + + public unsafe Statement(string sql) + { + Stmt = new libsql_stmt_t(); + var error = new Error(); + int exitCode; + + fixed (byte* sqlPtr = Encoding.UTF8.GetBytes(sql)) + { + fixed (libsql_stmt_t* statementPtr = &Stmt) + { + exitCode = Bindings.libsql_prepare(sqlPtr, statementPtr, &error.Ptr); + } + } + + error.ThrowIfNonZero(exitCode, $"Failed to prepare statement for: {sql}"); + } + + public unsafe void Bind(object[] values) + { + var error = new Error(); + int exitCode; + + if (values is null) + { + exitCode = Bindings.libsql_bind_null(Stmt, 1, &error.Ptr); + error.ThrowIfNonZero(exitCode, "Failed to bind null parameter"); + } + else + { + for (var i = 0; i < values.Length; i++) + { + var arg = values[i]; + + + if (arg is int val) { + exitCode = Bindings.libsql_bind_int(Stmt, i + 1, val, &error.Ptr); + } + else if (arg is double d) { + exitCode = Bindings.libsql_bind_float(Stmt, i + 1, d, &error.Ptr); + } + else if (arg is string s) { + fixed (byte* sPtr = Encoding.UTF8.GetBytes(s)) + { + exitCode = Bindings.libsql_bind_string(Stmt, i + 1, sPtr, &error.Ptr); + } + } + else if (arg is byte[] b) { + fixed (byte* bPtr = b) + { + exitCode = Bindings.libsql_bind_blob(Stmt, i + 1, bPtr, b.Length, &error.Ptr); + } + } + else if (arg is null) + { + exitCode = Bindings.libsql_bind_null(Stmt, i + 1, &error.Ptr); + } + else + { + throw new ArgumentException($"Unsupported argument type: {arg.GetType()}"); + } + + error.ThrowIfNonZero(exitCode, $"Failed to bind parameter. Type: {(arg is null ? "null" : arg.GetType().ToString())} Value: {arg}"); + } + } + } + + private void ReleaseUnmanagedResources() + { + Bindings.libsql_free_stmt(Stmt); + } + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~Statement() + { + ReleaseUnmanagedResources(); + } + } +}