Skip to content

Commit

Permalink
Fix for OdbcParameter Error "Data type 0x23 is a deprecated large obj…
Browse files Browse the repository at this point in the history
…ect..." (dotnet#24529)
  • Loading branch information
Gene Lee authored and Paulo Janotti committed Oct 31, 2017
1 parent 8b4ee42 commit 83232e2
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 15 deletions.
35 changes: 20 additions & 15 deletions src/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -747,26 +747,31 @@ internal void PrepareForBind(OdbcCommand command, short ordinal, ref int paramet
}
};

int cbParameterSize = GetParameterSize(value, offset, ordinal); // count of bytes for the data, for SQLBindParameter
int cbParameterSize = GetParameterSize(value, offset, ordinal); // count of bytes for the data, for SQLBindParameter

// here we upgrade the datatypes if the given values size is bigger than the types columnsize
//
// Upgrade input value type if the size of input value is bigger than the max size of the input value type.
switch (_bindtype._sql_type)
{
case ODBC32.SQL_TYPE.VARBINARY: // MDAC 74372
// Note: per definition DbType.Binary does not support more than 8000 bytes so we change the type for binding
if ((cbParameterSize > 8000))
{ _bindtype = TypeMap._Image; } // will change to LONGVARBINARY
case ODBC32.SQL_TYPE.VARBINARY:
// Max length of VARBINARY is 8,000 of byte array.
if (size > 8000)
{
_bindtype = TypeMap._Image; // will change to LONGVARBINARY
}
break;
case ODBC32.SQL_TYPE.VARCHAR: // MDAC 74372
// Note: per definition DbType.Binary does not support more than 8000 bytes so we change the type for binding
if ((cbParameterSize > 8000))
{ _bindtype = TypeMap._Text; } // will change to LONGVARCHAR
case ODBC32.SQL_TYPE.VARCHAR:
// Max length of VARCHAR is 8,000 of non-unicode characters.
if (size > 8000)
{
_bindtype = TypeMap._Text; // will change to LONGVARCHAR
}
break;
case ODBC32.SQL_TYPE.WVARCHAR: // MDAC 75099
// Note: per definition DbType.Binary does not support more than 8000 bytes so we change the type for binding
if ((cbParameterSize > 4000))
{ _bindtype = TypeMap._NText; } // will change to WLONGVARCHAR
case ODBC32.SQL_TYPE.WVARCHAR:
// Max length of WVARCHAR (NVARCHAR) is 4,000 of unicode characters.
if (size > 4000)
{
_bindtype = TypeMap._NText; // will change to WLONGVARCHAR
}
break;
}

Expand Down
270 changes: 270 additions & 0 deletions src/System.Data.Odbc/tests/OdbcParameterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
using System.Data.SqlClient;
using System.Text;
using Xunit;

namespace System.Data.Odbc.Tests
{
public static class OdbcParameterTests
{
[CheckConnStrSetupFact]
public static void RunTest()
{
string str1000 = null;
string str2000 = null;
string str4000 = null;
string str5000 = null;
string str8000 = "";
for (int i = 0; i < 800; i++)
{
str8000 += "0123456789";
if (i == 99)
{
str1000 = str8000;
}
else if (i == 199)
{
str2000 = str8000;
}
else if (i == 399)
{
str4000 = str8000;
}
else if (i == 499)
{
str5000 = str8000;
}
}

byte[] byte1000 = Encoding.ASCII.GetBytes(str1000);
byte[] byte2000 = Encoding.ASCII.GetBytes(str2000);
byte[] byte4000 = Encoding.ASCII.GetBytes(str4000);
byte[] byte5000 = Encoding.ASCII.GetBytes(str5000);
byte[] byte8000 = Encoding.ASCII.GetBytes(str8000);

object output = null;
int inputLength = 0;
int outputLength = 0;

RunTestProcedure("VARBINARY", 8000, byte8000, out output, out inputLength, out outputLength);
string outputStr = Encoding.ASCII.GetString(output as byte[]);
Assert.Equal(str8000, outputStr);
Assert.Equal(byte8000.Length, inputLength);
Assert.Equal(byte8000.Length, outputLength);

RunTestProcedure("VARBINARY", 8000, byte5000, out output, out inputLength, out outputLength);
outputStr = Encoding.ASCII.GetString(output as byte[]);
Assert.Equal(str8000, outputStr);
Assert.Equal(byte5000.Length, inputLength);
Assert.Equal(byte8000.Length, outputLength);

RunTestProcedure("VARBINARY", 8000, byte4000, out output, out inputLength, out outputLength);
outputStr = Encoding.ASCII.GetString(output as byte[]);
Assert.Equal(str8000, outputStr);
Assert.Equal(byte4000.Length, inputLength);
Assert.Equal(str8000.Length, outputLength);

RunTestProcedure("VARBINARY", 8000, byte2000, out output, out inputLength, out outputLength);
outputStr = Encoding.ASCII.GetString(output as byte[]);
Assert.Equal(str4000, outputStr);
Assert.Equal(byte2000.Length, inputLength);
Assert.Equal(str4000.Length, outputLength);

RunTestProcedure("VARBINARY", 8000, byte1000, out output, out inputLength, out outputLength);
outputStr = Encoding.ASCII.GetString(output as byte[]);
Assert.Equal(str2000, outputStr);
Assert.Equal(byte1000.Length, inputLength);
Assert.Equal(byte2000.Length, outputLength);

RunTestProcedure("VARCHAR", 8000, str8000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str8000, outputStr);
Assert.Equal(str8000.Length, inputLength);
Assert.Equal(str8000.Length, outputLength);

RunTestProcedure("VARCHAR", 8000, str5000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str8000, outputStr);
Assert.Equal(str5000.Length, inputLength);
Assert.Equal(str8000.Length, outputLength);

RunTestProcedure("VARCHAR", 8000, str4000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str8000, outputStr);
Assert.Equal(str4000.Length, inputLength);
Assert.Equal(str8000.Length, outputLength);

RunTestProcedure("VARCHAR", 8000, str2000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str4000, outputStr);
Assert.Equal(str2000.Length, inputLength);
Assert.Equal(str4000.Length, outputLength);

RunTestProcedure("VARCHAR", 8000, str1000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str2000, outputStr);
Assert.Equal(str1000.Length, inputLength);
Assert.Equal(str2000.Length, outputLength);

RunTestProcedure("NVARCHAR", 4000, str8000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str4000, outputStr);
Assert.Equal(str4000.Length * 2, inputLength); // since NVARCHAR takes 2 bytes per character
Assert.Equal(str4000.Length * 2, outputLength);

RunTestProcedure("NVARCHAR", 4000, str5000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str4000, outputStr);
Assert.Equal(str4000.Length * 2, inputLength);
Assert.Equal(str4000.Length * 2, outputLength);

RunTestProcedure("NVARCHAR", 4000, str4000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str4000, outputStr);
Assert.Equal(str4000.Length * 2, inputLength);
Assert.Equal(str4000.Length * 2, outputLength);

RunTestProcedure("NVARCHAR", 4000, str2000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str4000, outputStr);
Assert.Equal(str2000.Length * 2, inputLength);
Assert.Equal(str4000.Length * 2, outputLength);

RunTestProcedure("NVARCHAR", 4000, str1000, out output, out inputLength, out outputLength);
outputStr = output as string;
Assert.Equal(str2000, outputStr);
Assert.Equal(str1000.Length * 2, inputLength);
Assert.Equal(str2000.Length * 2, outputLength);
}

private static void RunTestProcedure(string procDataType, int procDataSize, object v1, out object v2, out int v3, out int v4)
{
string procName = DataTestUtility.GetUniqueName("ODBCTEST", "", "");

string removeExistingStoredProcSql =
$"IF OBJECT_ID('{procName}', 'P') IS NOT NULL " +
$"DROP PROCEDURE {procName};";

string createTestStoredProcSql =
$"CREATE PROCEDURE {procName} (" +
$"@v1 {procDataType}({procDataSize}), " +
$"@v2 {procDataType}({procDataSize}) OUT, " +
"@v3 INTEGER OUT, " +
"@v4 INTEGER OUT) " +
"AS BEGIN " +
"SET @v2 = @v1 + @v1; " +
"SET @v3 = datalength(@v1); " +
"SET @v4 = datalength(@v2); " +
"END;";

try
{
DataTestUtility.RunNonQuery(DataTestUtility.OdbcConnStr, removeExistingStoredProcSql);
DataTestUtility.RunNonQuery(DataTestUtility.OdbcConnStr, createTestStoredProcSql);

DbAccessor dbAccessUtil = new DbAccessor();
dbAccessUtil.connectSqlServer(DataTestUtility.OdbcConnStr);
dbAccessUtil.callProc("{ call "+ procName+"(?,?,?,?) }", procDataType, procDataSize, v1, out v2, out v3, out v4);
dbAccessUtil.commit();
dbAccessUtil.disconnect();
}
finally
{
DataTestUtility.RunNonQuery(DataTestUtility.OdbcConnStr, removeExistingStoredProcSql);
}
}

private class DbAccessor
{
private OdbcConnection con = null;
private OdbcTransaction trn = null;

public bool connectSqlServer(string connStr)
{
if (con == null)
{
con = new OdbcConnection(connStr);
}

con.Open();
trn = con.BeginTransaction();

return true;
}

public void disconnect()
{
if (trn != null)
{
trn.Rollback();
trn.Dispose();
trn = null;
}
if (con != null)
{
con.Close();
con.Dispose();
con = null;
}
}

public void callProc(string sql, string procDataType, int procDataSize, object v1, out object v2, out int v3, out int v4)
{
using (OdbcCommand command = new OdbcCommand(sql, con, trn))
{
command.Parameters.Clear();
command.CommandType = CommandType.StoredProcedure;

OdbcType dataType = OdbcType.NVarChar;
switch (procDataType.ToUpper())
{
case "VARBINARY":
dataType = OdbcType.VarBinary;
break;
case "VARCHAR":
dataType = OdbcType.VarChar;
break;
}

command.Parameters.Add("@v1", dataType, procDataSize);
command.Parameters.Add("@v2", dataType, procDataSize);
command.Parameters.Add("@v3", OdbcType.Int);
command.Parameters.Add("@v4", OdbcType.Int);

command.Parameters["@v1"].Direction = ParameterDirection.Input;
command.Parameters["@v2"].Direction = ParameterDirection.Output;
command.Parameters["@v3"].Direction = ParameterDirection.Output;
command.Parameters["@v4"].Direction = ParameterDirection.Output;

command.Parameters["@v1"].Value = v1;
command.ExecuteNonQuery();

v2 = command.Parameters["@v2"].Value;
v3 = Int32.Parse(command.Parameters["@v3"].Value.ToString());
v4 = Int32.Parse(command.Parameters["@v4"].Value.ToString());
}
}

public bool commit()
{
if (trn == null)
{
return false;
}
trn.Commit();
trn = null;
return true;
}

public bool rollback()
{
if (trn == null)
{
return false;
}
trn.Rollback();
trn = null;
return true;
}
}
}
}
3 changes: 3 additions & 0 deletions src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<Compile Include="CommandBuilderTests.cs" />
<Compile Include="ReaderTests.cs" />
<Compile Include="SmokeTest.cs" />
<Compile Include="TestCommon\DataTestUtility.cs" />
<Compile Include="TestCommon\CheckConnStrSetupFactAttribute.cs" />
<Compile Include="OdbcParameterTests.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="ConnectionStrings.Windows.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Xunit;

namespace System.Data.Odbc.Tests
{
public class CheckConnStrSetupFactAttribute : FactAttribute
{
public CheckConnStrSetupFactAttribute()
{
if(!DataTestUtility.AreConnStringsSetup())
{
Skip = "Connection Strings Not Setup";
}
}
}
}
48 changes: 48 additions & 0 deletions src/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;

namespace System.Data.Odbc.Tests
{
public static class DataTestUtility
{
public static readonly string OdbcConnStr = null;

static DataTestUtility()
{
OdbcConnStr = Environment.GetEnvironmentVariable("TEST_ODBC_CONN_STR");
}

public static bool AreConnStringsSetup()
{
return !string.IsNullOrEmpty(OdbcConnStr);
}

// the name length will be no more then (16 + prefix.Length + escapeLeft.Length + escapeRight.Length)
// some providers does not support names (Oracle supports up to 30)
public static string GetUniqueName(string prefix, string escapeLeft, string escapeRight)
{
string uniqueName = string.Format("{0}{1}_{2}_{3}{4}",
escapeLeft,
prefix,
DateTime.Now.Ticks.ToString("X", CultureInfo.InvariantCulture), // up to 8 characters
Guid.NewGuid().ToString().Substring(0, 6), // take the first 6 characters only
escapeRight);
return uniqueName;
}

public static void RunNonQuery(string connectionString, string sql)
{
using (OdbcConnection connection = new OdbcConnection(connectionString))
{
using (OdbcCommand command = new OdbcCommand(sql, connection))
{
connection.Open();
command.ExecuteNonQuery();
}
}
}
}
}

0 comments on commit 83232e2

Please sign in to comment.