Skip to content

Commit

Permalink
Support named parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Giorgi committed Sep 26, 2023
1 parent 87d4405 commit c500c1a
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public static class PreparedStatements
[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_nparams")]
public static extern long DuckDBParams(DuckDBPreparedStatement preparedStatement);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_bind_parameter_index")]
public static extern DuckDBState DuckDBBindParameterIndex(DuckDBPreparedStatement preparedStatement, out int index, string name);

[DllImport(DuckDbLibrary, CallingConvention = CallingConvention.Cdecl, EntryPoint = "duckdb_bind_boolean")]
public static extern DuckDBState DuckDBBindBoolean(DuckDBPreparedStatement preparedStatement, long index, bool val);

Expand Down
25 changes: 22 additions & 3 deletions DuckDB.NET.Data/Internal/PreparedStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Runtime.ExceptionServices;
using DuckDB.NET.Data.Extensions;
Expand Down Expand Up @@ -146,10 +147,28 @@ private static void BindParameters(DuckDBPreparedStatement preparedStatement, Du
throw new InvalidOperationException($"Invalid number of parameters. Expected {expectedParameters}, got {parameterCollection.Count}");
}

for (var i = 0; i < parameterCollection.Count; ++i)
if (parameterCollection.OfType<DuckDBParameter>().Any(p => !string.IsNullOrEmpty(p.ParameterName)))
{
var param = parameterCollection[i];
BindParameter(preparedStatement, i + 1, param);
foreach (DuckDBParameter param in parameterCollection)
{
var state = NativeMethods.PreparedStatements.DuckDBBindParameterIndex(preparedStatement, out var index, param.ParameterName);
if (state.IsSuccess())
{
BindParameter(preparedStatement, index, param);
}
else
{
throw new InvalidOperationException($"Cannot get parameter '{param.ParameterName}' index.");
}
}
}
else
{
for (var i = 0; i < parameterCollection.Count; ++i)
{
var param = parameterCollection[i];
BindParameter(preparedStatement, i + 1, param);
}
}
}

Expand Down
4 changes: 1 addition & 3 deletions DuckDB.NET.Test/Helpers/Defer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ public Defer() {}

public Defer(Action action)
{
AddAction(action);
actions.Push(action);
}

public void AddAction(Action action) => actions.Push(action);

public void Dispose()
{
Expand Down
86 changes: 63 additions & 23 deletions DuckDB.NET.Test/Parameters/ParameterCollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void BindSingleValueTest(string query)

var command = connection.CreateCommand();

command.Parameters.Add(new DuckDBParameter("test", 42));
command.Parameters.Add(new DuckDBParameter("1", 42));
command.CommandText = query;
var scalar = command.ExecuteScalar();
scalar.Should().Be(42);
Expand All @@ -39,7 +39,7 @@ public void BindSingleValueNullTest(string query)

var command = connection.CreateCommand();

command.Parameters.Add(new DuckDBParameter("test", null));
command.Parameters.Add(new DuckDBParameter("1", null));
command.CommandText = query;
var scalar = command.ExecuteScalar();
scalar.Should().Be(DBNull.Value);
Expand Down Expand Up @@ -160,8 +160,30 @@ public void BindMultipleValuesTest(string queryStatement)
command.ExecuteNonQuery();

command.CommandText = queryStatement;
command.Parameters.Add(new DuckDBParameter("param1", 42));
command.Parameters.Add(new DuckDBParameter("param2", "hello"));
command.Parameters.Add(new DuckDBParameter("1", 42));
command.Parameters.Add(new DuckDBParameter("2", "hello"));
var affectedRows = command.ExecuteNonQuery();
affectedRows.Should().NotBe(0);
}

[Theory]
[InlineData("INSERT INTO ParametersTestKeyValue (KEY, VALUE) VALUES ($key, $value)")]
[InlineData("UPDATE ParametersTestKeyValue SET KEY = $key, VALUE = $value;")]
public void BindMultipleValuesTestNamedParameters(string queryStatement)
{
using var connection = new DuckDBConnection("DataSource=:memory:");
using var defer = new Defer(() => connection.Execute("DROP TABLE ParametersTestKeyValue;"));
connection.Open();

var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE ParametersTestKeyValue (KEY INTEGER, VALUE TEXT)";
command.ExecuteNonQuery();
command.CommandText = "INSERT INTO ParametersTestKeyValue (KEY, VALUE) VALUES (42, 'test string');";
command.ExecuteNonQuery();

command.CommandText = queryStatement;
command.Parameters.Add(new DuckDBParameter("key", 42));
command.Parameters.Add(new DuckDBParameter("value", "hello"));
var affectedRows = command.ExecuteNonQuery();
affectedRows.Should().NotBe(0);
}
Expand Down Expand Up @@ -192,25 +214,43 @@ public void BindMultipleValuesInvalidOrderTest(string queryStatement)

[Theory]
// Dapper supports ? placeholders when using both DynamicParameters and an object
[InlineData("INSERT INTO DapperParatemersObjectBindingTest VALUES (?, ?);")]
[InlineData("UPDATE DapperParatemersObjectBindingTest SET a = ?, b = ?;")]
[InlineData("INSERT INTO DapperParametersObjectBindingTest VALUES (?, ?);")]
[InlineData("UPDATE DapperParametersObjectBindingTest SET a = ?, b = ?;")]
public void BindDapperWithObjectTest(string queryStatement)
{
using var connection = new DuckDBConnection("DataSource=:memory:");
using var defer = new Defer(() => connection.Execute("DROP TABLE DapperParatemersObjectBindingTest;"));
using var defer = new Defer(() => connection.Execute("DROP TABLE DapperParametersObjectBindingTest;"));
connection.Open();

connection.Execute("CREATE TABLE DapperParatemersObjectBindingTest (a INTEGER, b TEXT);");
connection.Execute("CREATE TABLE DapperParametersObjectBindingTest (a INTEGER, b TEXT);");
connection.Execute("INSERT INTO DapperParametersObjectBindingTest (a, b) VALUES (42, 'test string');");

var dp = new DynamicParameters();
dp.Add("param2", 1);
dp.Add("param1", "test");
dp.Add("?1", 1);
dp.Add("?2", "test");

connection.Execute(queryStatement, dp).Should().BeLessOrEqualTo(1);
connection.Execute(queryStatement, new { A = 1, B = "test" });
connection.Execute(queryStatement, dp).Should().BeGreaterOrEqualTo(1);
}

connection.Execute(queryStatement, dp).Should().BeLessOrEqualTo(1);
connection.Execute(queryStatement, new { A = 1, B = "test" });
[Theory]
// Dapper supports ? placeholders when using both DynamicParameters and an object
[InlineData("INSERT INTO DapperParametersObjectBindingTest VALUES ($foo, $bar);")]
[InlineData("UPDATE DapperParametersObjectBindingTest SET a = $foo, b = $bar;")]
public void BindDapperWithObjectTestNamesParameters(string queryStatement)
{
using var connection = new DuckDBConnection("DataSource=:memory:");
using var defer = new Defer(() => connection.Execute("DROP TABLE DapperParametersObjectBindingTest;"));
connection.Open();

connection.Execute("CREATE TABLE DapperParametersObjectBindingTest (a INTEGER, b TEXT);");
connection.Execute("INSERT INTO DapperParametersObjectBindingTest (a, b) VALUES (42, 'test string');");

var dp = new DynamicParameters();
dp.Add("foo", 1);
dp.Add("bar", "test");

connection.Execute(queryStatement, dp).Should().BeGreaterOrEqualTo(1);
//connection.Execute(queryStatement, new { foo = 1, bar = "test" }).Should().BeGreaterOrEqualTo(1, "Passing parameters as object should work");
}

[Theory]
Expand All @@ -228,8 +268,8 @@ public void BindDapperDynamicParamsOnlyTest(string queryStatement)
connection.Execute("CREATE TABLE DapperParametersDynamicParamsBindingTest (a INTEGER, b TEXT);");

var dp = new DynamicParameters();
dp.Add("param2", 1);
dp.Add("param1", "test");
dp.Add("1", 1);
dp.Add("2", "test");

connection.Execute(queryStatement, dp).Should().BeLessOrEqualTo(1);
}
Expand All @@ -244,24 +284,24 @@ public void BindSingleValueDapperNullTest(string query)
connection.Open();

var parameters = new DynamicParameters();
parameters.Add("test", null);
parameters.Add("1", null);
var scalar = connection.QuerySingle<long?>(query, parameters);
scalar.Should().BeNull();
}

[Theory]
// Dapper does not support such placeholders when using an object :(
[InlineData("INSERT INTO DapperParametersObjectBindingFaileTest VALUES (?1, ?2);")]
[InlineData("INSERT INTO DapperParametersObjectBindingFaileTest VALUES ($1, $2);")]
[InlineData("UPDATE DapperParametersObjectBindingFaileTest SET a = ?1, b = ?2;")]
[InlineData("UPDATE DapperParametersObjectBindingFaileTest SET a = $1, b = $2;")]
[InlineData("INSERT INTO DapperParametersObjectBindingFailTest VALUES (?1, ?2);")]
[InlineData("INSERT INTO DapperParametersObjectBindingFailTest VALUES ($1, $2);")]
[InlineData("UPDATE DapperParametersObjectBindingFailTest SET a = ?1, b = ?2;")]
[InlineData("UPDATE DapperParametersObjectBindingFailTest SET a = $1, b = $2;")]
public void BindDapperObjectFailuresTest(string queryStatement)
{
using var connection = new DuckDBConnection("DataSource=:memory:");
using var defer = new Defer(() => connection.Execute("DROP TABLE DapperParametersObjectBindingFaileTest;"));
using var defer = new Defer(() => connection.Execute("DROP TABLE DapperParametersObjectBindingFailTest;"));
connection.Open();

connection.Execute("CREATE TABLE DapperParametersObjectBindingFaileTest (a INTEGER, b TEXT);");
connection.Execute("CREATE TABLE DapperParametersObjectBindingFailTest (a INTEGER, b TEXT);");

connection.Invoking(con => con.Execute(queryStatement, new { param1 = 1, param2 = "hello" }))
.Should().ThrowExactly<InvalidOperationException>();
Expand Down

0 comments on commit c500c1a

Please sign in to comment.