Skip to content
Closed
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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="11.0.0-preview.5.26302.115" />
<PackageReference Include="CsvHelper" Version="33.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="DuckDB.NET.Data.Full" Version="1.4.4" />
<PackageReference Include="DuckDB.NET.Data.Full" Version="1.5.3" />
</ItemGroup>
</Project>
110 changes: 13 additions & 97 deletions DuckDB-Memory-Lib/ConnectionManager.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
using System.Data;
using DuckDB.NET.Data;
using MemoryDb_Lib.Shared;


namespace DuckDb_Memory_Lib;

public sealed class ConnectionManager
public sealed class ConnectionManager : ConnectionManagerBase<DuckDBConnection>
{
private static readonly Lazy<ConnectionManager> LazyInstance =
new(() => new ConnectionManager(), LazyThreadSafetyMode.ExecutionAndPublication);

private readonly object _syncRoot = new();
private readonly Dictionary<string, DuckDBConnection> _connections = new(StringComparer.OrdinalIgnoreCase);

private ConnectionManager() { }

/// <summary>
Expand All @@ -23,98 +20,15 @@ public static ConnectionManager GetInstance()
}

/// <summary>
/// Retrieves an open SQLite connection identified by the provided alias, creating it when necessary.
/// Retrieves an open DuckDB connection identified by the provided alias, creating it when necessary.
/// </summary>
public DuckDBConnection GetConnection(string? alias = null, string? path = null)
{
var normalizedAlias = NormalizeAlias(alias);

lock (_syncRoot)
{
if (_connections.TryGetValue(normalizedAlias, out var existingConnection))
{
EnsureOpen(existingConnection);
return existingConnection;
}

var newConnection = DuckTools.GetInstance(path);
EnsureOpen(newConnection);
_connections[normalizedAlias] = newConnection;

return newConnection;
}
}

/// <summary>
/// Closes and disposes the connection associated with the provided alias.
/// </summary>
public void CloseConnection(string? alias = null)
{
var normalizedAlias = NormalizeAlias(alias);

lock (_syncRoot)
{
if (!_connections.TryGetValue(normalizedAlias, out var connection))
{
return;
}

try
{
connection.Close();
}
finally
{
connection.Dispose();
_connections.Remove(normalizedAlias);
}
}
}

/// <summary>
/// Closes and disposes all active SQLite connections managed by this instance.
/// </summary>
public void CloseAllConnections()
{
lock (_syncRoot)
{
foreach (var connection in _connections.Values)
{
try
{
connection.Close();
}
finally
{
connection.Dispose();
}
}

_connections.Clear();
}
return GetConnectionCore(alias, path, DuckTools.GetInstance);
}

/// <summary>
/// Opens the provided SQLite connection when it is not already open.
/// </summary>
private static void EnsureOpen(DuckDBConnection connection)
{
if (connection.State != ConnectionState.Open)
{
connection.Open();
}
}

/// <summary>
/// Normalizes aliases to ensure consistent lookups inside the connection dictionary.
/// </summary>
private static string NormalizeAlias(string? alias)
{
return string.IsNullOrWhiteSpace(alias) ? "default" : alias.Trim();
}

/// <summary>
/// Static helper that delegates to <see cref="CloseConnection(string?)"/>.
/// Static helper that delegates to <see cref="ConnectionManagerBase{TConnection}.CloseConnection(string?)"/>.
/// </summary>
public static void Close(string? alias = null)
{
Expand All @@ -128,7 +42,7 @@ public static void CloseAll()
{
GetInstance().CloseAllConnections();
}
}
}

public sealed class KeeperRegisterIdDataBase
{
Expand Down Expand Up @@ -182,11 +96,13 @@ public static void Register(string path, string idDataBase)
/// </summary>
public static void DeleteRegister(string idDataBase)
{
var keepIdDataBases = _mapIdDataBase.Values.ToList();
int indexValues = keepIdDataBases.IndexOf(idDataBase);
string pathIdDataBase = _mapIdDataBase.Keys.ToList()[indexValues];

_mapIdDataBase.Remove(pathIdDataBase);
var pathIdDataBase = _mapIdDataBase
.FirstOrDefault(entry => string.Equals(entry.Value, idDataBase, StringComparison.OrdinalIgnoreCase))
.Key;

if (pathIdDataBase is not null)
{
_mapIdDataBase.Remove(pathIdDataBase);
}
}
}
8 changes: 6 additions & 2 deletions DuckDB-Memory-Lib/DuckDB-Memory-Lib.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>DuckDb_Memory_Lib</RootNamespace>
Expand All @@ -7,7 +7,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Update="Microsoft.Data.Sqlite" Version="11.0.0-preview.5.26302.115" />
<PackageReference Update="DuckDB.NET.Data.Full" Version="1.4.4" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shared\ConnectionManagerBase.cs" Link="Shared\ConnectionManagerBase.cs" />
</ItemGroup>

</Project>
7 changes: 3 additions & 4 deletions DuckDB-Memory-Lib/DuckTools.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using DuckDB.NET.Data;
using DuckDB.NET.Data.DataChunk.Reader;
using DuckDB.NET.Data.DataChunk.Writer;
Expand Down Expand Up @@ -128,8 +128,7 @@ public static EnumsDuckMemory.Output RegisterScalarFunction<TInput, TOutput>(
{
db.RegisterScalarFunction<TInput, TOutput>(
idFunction,
func,
isPureFunction: true);
func);

return EnumsDuckMemory.Output.SUCCESS;
}
Expand Down Expand Up @@ -177,7 +176,7 @@ public static EnumsDuckMemory.Output AttachedDataBase(DuckDBConnection db, strin
}
else
{
attachedQry = $"ATTACH '{strConnection}' AS \"{aliasDataBase}\" ";
attachedQry = $"ATTACH '{strConnection}' AS {QuoteIdentifier(aliasDataBase)}";
}

try
Expand Down
60 changes: 59 additions & 1 deletion DuckDB-Memory-Lib/DuckTypes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using DuckDB.NET.Native;
using System.Data;
using DuckDB.NET.Native;

namespace DuckDb_Memory_Lib;

Expand Down Expand Up @@ -76,4 +77,61 @@ public static (object? Value, Type NetType) StrTryParse(string? value)
// Fallback → VARCHAR
return (value, typeof(string));
}

/// <summary>
/// Maps a .NET type to the closest ADO.NET DbType.
/// The resulting DbType can be used by DuckDB.NET to infer the native DuckDB type.
/// </summary>
public static DbType GetDbType(Type type)
{
type = Nullable.GetUnderlyingType(type) ?? type;

if (type == typeof(bool))
return DbType.Boolean;

if (type == typeof(byte))
return DbType.Byte;

if (type == typeof(short))
return DbType.Int16;

if (type == typeof(int))
return DbType.Int32;

if (type == typeof(long))
return DbType.Int64;

if (type == typeof(float))
return DbType.Single;

if (type == typeof(double))
return DbType.Double;

if (type == typeof(decimal))
return DbType.Decimal;

if (type == typeof(DateOnly))
return DbType.Date;

if (type == typeof(TimeOnly))
return DbType.Time;

if (type == typeof(DateTime))
return DbType.DateTime;

if (type == typeof(Guid))
return DbType.Guid;

if (type == typeof(byte[]))
return DbType.Binary;

if (type == typeof(string))
return DbType.String;

throw new NotSupportedException(
$"The .NET type '{type.FullName}' cannot be mapped to a DbType.");
}
}



45 changes: 33 additions & 12 deletions DuckDB-Memory-Lib/QueryExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Diagnostics;
using DuckDB.NET.Data;
using System.IO;
using DuckDB.NET.Native;

namespace DuckDb_Memory_Lib;

Expand Down Expand Up @@ -30,15 +31,16 @@ public static EnumsDuckMemory.Output CreateTable(DuckDBConnection db, string idD
for (var i = 0; i < noFields; i++)
{
var t = NetTypeToDuckDbType.GetDuckDbType(types[i]);
fieldsDefinition.Add(headers[i] + " " + t);
fieldsDefinition.Add(DuckTools.QuoteIdentifier(headers[i]) + " " + t);
}

}

try
{
var qry = $"CREATE TABLE IF NOT EXISTS {idDataBase}.{idTable}({string.Join(",", fieldsDefinition)})";
var cmd = new DuckDBCommand(qry, db);
var tableName = $"{DuckTools.QuoteIdentifier(idDataBase)}.{DuckTools.QuoteIdentifier(idTable)}";
var qry = $"CREATE TABLE IF NOT EXISTS {tableName}({string.Join(",", fieldsDefinition)})";
using var cmd = new DuckDBCommand(qry, db);
cmd.ExecuteNonQuery();

return EnumsDuckMemory.Output.SUCCESS;
Expand Down Expand Up @@ -71,7 +73,7 @@ public static EnumsDuckMemory.Output CreateParquetTable(DuckDBConnection db, str
var tableName = $"{DuckTools.QuoteIdentifier(idDataBase)}.{DuckTools.QuoteIdentifier(idTable)}";
var parquetPath = parquetPathFile.Replace("'", "''");
var qry = $"CREATE OR REPLACE TABLE {tableName} AS SELECT * FROM '{parquetPath}';";
var cmd = new DuckDBCommand(qry, db);
using var cmd = new DuckDBCommand(qry, db);
cmd.ExecuteNonQuery();

return EnumsDuckMemory.Output.SUCCESS;
Expand All @@ -90,8 +92,8 @@ public static (EnumsDuckMemory.Output, List<Dictionary<string, object>>) Execute
{
try
{
var cmd = new DuckDBCommand(qry, db);
var qryResult = cmd.ExecuteReader();
using var cmd = new DuckDBCommand(qry, db);
using var qryResult = cmd.ExecuteReader();

var resultList = new List<Dictionary<string, object>>();

Expand All @@ -104,7 +106,6 @@ public static (EnumsDuckMemory.Output, List<Dictionary<string, object>>) Execute
}
}

qryResult.Close();
return (EnumsDuckMemory.Output.SUCCESS, resultList);
}
catch (Exception ex)
Expand All @@ -121,14 +122,34 @@ public static (EnumsDuckMemory.Output, List<Dictionary<string, object>>) Execute
{
try
{
qry = parameters.Keys.Aggregate(qry,
(current, param) => current.Replace(param, parameters[param], StringComparison.OrdinalIgnoreCase));
return ExecuteQryReader(db, qry);
using var cmd = new DuckDBCommand(qry, db);
foreach (var parameter in parameters)
{
var dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = parameter.Key;
dbParameter.Value = parameter.Value;
dbParameter.DbType = NetTypeToDuckDbType.GetDbType(parameter.Value.GetType());
cmd.Parameters.Add(dbParameter);
}

using var qryResult = cmd.ExecuteReader();
var resultList = new List<Dictionary<string, object>>();

if (qryResult.HasRows)
{
while (qryResult.Read())
{
resultList.Add(Enumerable.Range(0, qryResult.FieldCount)
.ToDictionary(qryResult.GetName, qryResult.GetValue));
}
}

return (EnumsDuckMemory.Output.SUCCESS, resultList);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
throw new Exception($"{ex.Message}-{EnumsDuckMemory.Output.ERROR_TO_EXECUTE_QUERY}", ex);
return (EnumsDuckMemory.Output.ERROR_TO_EXECUTE_QUERY, []);
}
}
}
Loading
Loading