Skip to content

Commit

Permalink
Database: Oracle Ref Cursor support (24.11) (#435)
Browse files Browse the repository at this point in the history
Allow the type of the parameter to be changed
Change the type of the parameter to OracleDbType.RefCursor
Change the type of the parameter for a few known types
  • Loading branch information
viogroza authored Jul 23, 2024
1 parent b787429 commit 78c1ce6
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
{
throw new ArgumentException(Resources.TimeoutMSException, "TimeoutMS");
}
Dictionary<string, Tuple<object, ArgumentDirection>> parameters = null;
Dictionary<string, ParameterInfo> parameters = null;
var continueOnError = ContinueOnError.Get(context);
try
{
Expand All @@ -61,10 +61,10 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA

if (Parameters != null)
{
parameters = new Dictionary<string, Tuple<object, ArgumentDirection>>();
parameters = new Dictionary<string, ParameterInfo>();
foreach (var param in Parameters)
{
parameters.Add(param.Key, new Tuple<object, ArgumentDirection>(param.Value.Get(context), param.Value.Direction));
parameters.Add(param.Key, new ParameterInfo() { Value = param.Value.Get(context), Direction = param.Value.Direction, Type = param.Value.ArgumentType });
}
}
ConnectionHelper.ConnectionValidation(existingConnection, connSecureString, connString, provName);
Expand Down Expand Up @@ -105,7 +105,7 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
var currentParam = Parameters[param.Key];
if (currentParam.Direction == ArgumentDirection.Out || currentParam.Direction == ArgumentDirection.InOut)
{
currentParam.Set(asyncCodeActivityContext, param.Value.Item1);
currentParam.Set(asyncCodeActivityContext, param.Value.Value);
}
}
};
Expand All @@ -115,15 +115,15 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
private class DBExecuteCommandResult
{
public int Result { get; }
public Dictionary<string, Tuple<object, ArgumentDirection>> ParametersBind { get; }
public Dictionary<string, ParameterInfo> ParametersBind { get; }

public DBExecuteCommandResult()
{
this.Result = 0;
this.ParametersBind = new Dictionary<string, Tuple<object, ArgumentDirection>>();
this.ParametersBind = new Dictionary<string, ParameterInfo>();
}

public DBExecuteCommandResult(int result, Dictionary<string, Tuple<object, ArgumentDirection>> parametersBind)
public DBExecuteCommandResult(int result, Dictionary<string, ParameterInfo> parametersBind)
{
this.Result = result;
this.ParametersBind = parametersBind;
Expand Down
17 changes: 10 additions & 7 deletions Activities/Database/UiPath.Database.Activities/ExecuteQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
{
throw new ArgumentException(Resources.TimeoutMSException, "TimeoutMS");
}
Dictionary<string, Tuple<object, ArgumentDirection>> parameters = null;
Dictionary<string, ParameterInfo> parameters = null;
var continueOnError = ContinueOnError.Get(context);
try
{
Expand All @@ -62,10 +62,13 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
ConnectionHelper.ConnectionValidation(existingConnection, connSecureString, connString, provName);
if (Parameters != null)
{
parameters = new Dictionary<string, Tuple<object, ArgumentDirection>>();
parameters = new Dictionary<string, ParameterInfo>();
foreach (var param in Parameters)
{
parameters.Add(param.Key, new Tuple<object, ArgumentDirection>(param.Value.Get(context), param.Value.Direction));
parameters.Add(param.Key, new ParameterInfo() {
Value = param.Value.Get(context),
Direction = param.Value.Direction,
Type = param.Value.ArgumentType});
}
}

Expand Down Expand Up @@ -107,7 +110,7 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
var currentParam = Parameters[param.Key];
if (currentParam.Direction == ArgumentDirection.Out || currentParam.Direction == ArgumentDirection.InOut)
{
currentParam.Set(asyncCodeActivityContext, param.Value.Item1);
currentParam.Set(asyncCodeActivityContext, param.Value.Value);
}
}
};
Expand All @@ -116,15 +119,15 @@ protected async override Task<Action<AsyncCodeActivityContext>> ExecuteInternalA
private class DBExecuteQueryResult
{
public DataTable Result { get; }
public Dictionary<string, Tuple<object, ArgumentDirection>> ParametersBind { get; }
public Dictionary<string, ParameterInfo> ParametersBind { get; }

public DBExecuteQueryResult()
{
this.Result = new DataTable();
this.ParametersBind = new Dictionary<string, Tuple<object, ArgumentDirection>>();
this.ParametersBind = new Dictionary<string, ParameterInfo>();
}

public DBExecuteQueryResult(DataTable result, Dictionary<string, Tuple<object, ArgumentDirection>> parametersBind)
public DBExecuteQueryResult(DataTable result, Dictionary<string, ParameterInfo> parametersBind)
{
this.Result = result;
this.ParametersBind = parametersBind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ public void TestSize(string provider)
param.SetReturnsDefault(ParameterDirection.InputOutput);

var databaseConnection = new DatabaseConnection().Initialize(con.Object);
var parameters = new Dictionary<string, Tuple<object, ArgumentDirection>>() { { "param1", new Tuple<object, ArgumentDirection>("", ArgumentDirection.Out) } };
var parameters = new Dictionary<string, ParameterInfo>() {
{ "param1", new ParameterInfo() {Value = "", Direction = ArgumentDirection.Out}
}
};
databaseConnection.ExecuteQuery("TestProcedure", parameters, 0);
if (provider.ToLower().Contains("oracle"))
Assert.True(param.Object.Size == 1000000);
Expand Down
74 changes: 65 additions & 9 deletions Activities/Database/UiPath.Database/DatabaseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using UiPath.Database.Properties;
using UiPath.Data.ConnectionUI.Dialog.Workaround;
using UiPath.Robot.Activities.Api;
using Oracle.ManagedDataAccess.Types;

namespace UiPath.Database
{
Expand Down Expand Up @@ -57,7 +58,7 @@ public DatabaseConnection Initialize(string connectionString, string providerNam
DbProviderFactories.RegisterFactory("Microsoft.Data.SqlClient", Microsoft.Data.SqlClient.SqlClientFactory.Instance);

//OLEDB driver is Windows propietary - there is no support for other OS
if(_isWindows)
if (_isWindows)
DbProviderFactories.RegisterFactory("System.Data.OleDb", System.Data.OleDb.OleDbFactory.Instance);

DbProviderFactories.RegisterFactory("System.Data.Odbc", System.Data.Odbc.OdbcFactory.Instance);
Expand Down Expand Up @@ -85,7 +86,7 @@ public virtual void BeginTransaction()
_transaction = _connection.BeginTransaction();
}

public virtual DataTable ExecuteQuery(string sql, Dictionary<string, Tuple<object, ArgumentDirection>> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
public virtual DataTable ExecuteQuery(string sql, Dictionary<string, ParameterInfo> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
{
OpenConnection();
SetupCommand(sql, parameters, commandTimeout, commandType);
Expand All @@ -95,12 +96,13 @@ public virtual DataTable ExecuteQuery(string sql, Dictionary<string, Tuple<objec
foreach (var param in _command.Parameters)
{
var dbParam = param as DbParameter;
parameters[dbParam.ParameterName] = new Tuple<object, ArgumentDirection>(dbParam.Value, WokflowParameterDirectionToDbParameter(dbParam.Direction));
parameters[dbParam.ParameterName] = new ParameterInfo() { Value = dbParam.Value,
Direction = WokflowParameterDirectionToDbParameter(dbParam.Direction) };
}
return dt;
}

public virtual int Execute(string sql, Dictionary<string, Tuple<object, ArgumentDirection>> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
public virtual int Execute(string sql, Dictionary<string, ParameterInfo> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
{
OpenConnection();
SetupCommand(sql, parameters, commandTimeout, commandType);
Expand All @@ -109,7 +111,11 @@ public virtual int Execute(string sql, Dictionary<string, Tuple<object, Argument
foreach (var param in _command.Parameters)
{
var dbParam = param as DbParameter;
parameters[dbParam.ParameterName] = new Tuple<object, ArgumentDirection>(dbParam.Value, WokflowParameterDirectionToDbParameter(dbParam.Direction));
parameters[dbParam.ParameterName] = new ParameterInfo()
{
Value = dbParam.Value,
Direction = WokflowParameterDirectionToDbParameter(dbParam.Direction)
};
}
return result;
}
Expand All @@ -124,7 +130,7 @@ public virtual int InsertDataTable(string tableName, DataTable dataTable)
{
return InsertDataTableInternal(tableName, dataTable, true);
}
catch(Exception e)
catch (Exception e)
{
firstException = e;
}
Expand Down Expand Up @@ -442,7 +448,7 @@ private void OpenConnection()
}
}

private void SetupCommand(string sql, Dictionary<string, Tuple<object, ArgumentDirection>> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
private void SetupCommand(string sql, Dictionary<string, ParameterInfo> parameters, int commandTimeout, CommandType commandType = CommandType.Text)
{
if (_connection == null)
{
Expand All @@ -461,6 +467,7 @@ private void SetupCommand(string sql, Dictionary<string, Tuple<object, ArgumentD
_command.CommandType = commandType;
_command.CommandText = sql;
_command.Parameters.Clear();

if (parameters == null)
{
return;
Expand All @@ -469,13 +476,16 @@ private void SetupCommand(string sql, Dictionary<string, Tuple<object, ArgumentD
{
DbParameter dbParameter = _command.CreateParameter();
dbParameter.ParameterName = param.Key;
dbParameter.Direction = WokflowDbParameterToParameterDirection(param.Value.Item2);
dbParameter.Direction = WokflowDbParameterToParameterDirection(param.Value.Direction);
if (dbParameter.Direction.HasFlag(ParameterDirection.InputOutput) || dbParameter.Direction.HasFlag(ParameterDirection.Output))
{
dbParameter.Size = GetParameterSize(dbParameter);
}

dbParameter.Value = param.Value.Item1 ?? DBNull.Value;
dbParameter.Value = param.Value.Value ?? DBNull.Value;

UpdateDbParamType(dbParameter, param.Value);

_command.Parameters.Add(dbParameter);
}
}
Expand Down Expand Up @@ -551,5 +561,51 @@ private static ArgumentDirection WokflowParameterDirectionToDbParameter(Paramete
throw new ArgumentException(Resources.ParameterDirectionArgumentException);
}
}

private void UpdateDbParamType(DbParameter dbParameter, ParameterInfo parameterInfo)
{
if (parameterInfo?.Type is null)
return;
else if (UpdateDbParamTypeOracle(dbParameter, parameterInfo))
return; //in the futuree we might consider update param type for other providers
}

private bool UpdateDbParamTypeOracle(DbParameter dbParameter, ParameterInfo parameterInfo)
{
if (dbParameter is OracleParameter oracleParameter && _oracleMappings.TryGetValue(parameterInfo.Type, out var oracleType))
{
oracleParameter.OracleDbType = oracleType;
return true;
}

return false;
}


/// <summary>
/// The mapping of C# Type to OracleDbType or to DbType is not 1-1
/// There are a lot of conversions behind the scene that are done
/// Most of the conversions work ok with string type
/// Here is some reference (it might not be up to date)
/// https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings
/// https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/oracle-data-type-mappings
/// For now leave only a few conversions
/// </summary>
private readonly Dictionary<Type, OracleDbType> _oracleMappings = new Dictionary<Type, OracleDbType>()
{
{ typeof(OracleRefCursor), OracleDbType.RefCursor },
{ typeof(bool), OracleDbType.Boolean },
{ typeof(int), OracleDbType.Int32 },
{ typeof(uint), OracleDbType.Int32 },
{ typeof(short), OracleDbType.Int16 },
{ typeof(ushort), OracleDbType.Int16 },
{ typeof(long), OracleDbType.Int64 },
{ typeof(ulong), OracleDbType.Int64 },
{ typeof(byte), OracleDbType.Byte },
{ typeof(sbyte), OracleDbType.Byte },
{ typeof(float), OracleDbType.Single },
{ typeof(double), OracleDbType.Double },
{ typeof(decimal), OracleDbType.Decimal }
};
}
}
17 changes: 17 additions & 0 deletions Activities/Database/UiPath.Database/ParameterInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Activities;

namespace UiPath.Database
{
/// <summary>
/// Information about a parameter that will bind to a query
/// </summary>
public class ParameterInfo
{
public object Value { get; set; }

public Type Type { get; set; }

public ArgumentDirection Direction { get; set; }
}
}

0 comments on commit 78c1ce6

Please sign in to comment.