From 83232e21ae0515b6fa60410aabbf7c1ce01d4eda Mon Sep 17 00:00:00 2001 From: Gene Lee Date: Tue, 10 Oct 2017 16:23:13 -0700 Subject: [PATCH] Fix for OdbcParameter Error "Data type 0x23 is a deprecated large object..." (#24529) --- .../src/System/Data/Odbc/OdbcParameter.cs | 35 ++- .../tests/OdbcParameterTests.cs | 270 ++++++++++++++++++ .../tests/System.Data.Odbc.Tests.csproj | 3 + .../CheckConnStrSetupFactAttribute.cs | 19 ++ .../tests/TestCommon/DataTestUtility.cs | 48 ++++ 5 files changed, 360 insertions(+), 15 deletions(-) create mode 100644 src/System.Data.Odbc/tests/OdbcParameterTests.cs create mode 100644 src/System.Data.Odbc/tests/TestCommon/CheckConnStrSetupFactAttribute.cs create mode 100644 src/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs diff --git a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs index 21645ea5bf74..dbbf8b0be299 100644 --- a/src/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs +++ b/src/System.Data.Odbc/src/System/Data/Odbc/OdbcParameter.cs @@ -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; } diff --git a/src/System.Data.Odbc/tests/OdbcParameterTests.cs b/src/System.Data.Odbc/tests/OdbcParameterTests.cs new file mode 100644 index 000000000000..5aab7c759f07 --- /dev/null +++ b/src/System.Data.Odbc/tests/OdbcParameterTests.cs @@ -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; + } + } + } +} diff --git a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj index 18b4bb54b850..160eac53d64e 100644 --- a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj +++ b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj @@ -13,6 +13,9 @@ + + + diff --git a/src/System.Data.Odbc/tests/TestCommon/CheckConnStrSetupFactAttribute.cs b/src/System.Data.Odbc/tests/TestCommon/CheckConnStrSetupFactAttribute.cs new file mode 100644 index 000000000000..145bc2f11cf0 --- /dev/null +++ b/src/System.Data.Odbc/tests/TestCommon/CheckConnStrSetupFactAttribute.cs @@ -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"; + } + } + } +} diff --git a/src/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs b/src/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs new file mode 100644 index 000000000000..c7af7aa1fad7 --- /dev/null +++ b/src/System.Data.Odbc/tests/TestCommon/DataTestUtility.cs @@ -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(); + } + } + } + } +}