diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnums.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnums.cs index 656858b1fa..e07a449607 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -278,6 +278,7 @@ internal static MetaType GetMetaTypeFromDbType(DbType target) case DbType.Currency: return s_metaMoney; case DbType.Date: + return s_metaDate; case DbType.DateTime: return s_metaDateTime; case DbType.Decimal: @@ -301,7 +302,7 @@ internal static MetaType GetMetaTypeFromDbType(DbType target) case DbType.StringFixedLength: return s_metaNChar; case DbType.Time: - return s_metaDateTime; + return MetaTime; case DbType.Xml: return MetaXml; case DbType.DateTime2: diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs index 81d4b47210..77a15904ae 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -330,11 +330,7 @@ public override DbType DbType set { MetaType metatype = _metaType; - if ((null == metatype) || (metatype.DbType != value) || - // Two special datetime cases for backward compat - // DbType.Date and DbType.Time should always be treated as setting DbType.DateTime instead - value == DbType.Date || - value == DbType.Time) + if ((null == metatype) || (metatype.DbType != value)) { PropertyTypeChanging(); _metaType = MetaType.GetMetaTypeFromDbType(value); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnums.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnums.cs index 5fe6338173..288eead39f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -279,6 +279,7 @@ internal static MetaType GetMetaTypeFromDbType(DbType target) case DbType.Currency: return MetaMoney; case DbType.Date: + return MetaDate; case DbType.DateTime: return MetaDateTime; case DbType.Decimal: @@ -302,7 +303,7 @@ internal static MetaType GetMetaTypeFromDbType(DbType target) case DbType.StringFixedLength: return MetaNChar; case DbType.Time: - return MetaDateTime; + return MetaTime; case DbType.Xml: return MetaXml; case DbType.DateTime2: diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs index 21a4266759..1648e9f24d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -343,11 +343,7 @@ override public DbType DbType set { MetaType metatype = _metaType; - if ((null == metatype) || (metatype.DbType != value) || - // SQLBU 504029: Two special datetime cases for backward compat - // DbType.Date and DbType.Time should always be treated as setting DbType.DateTime instead - value == DbType.Date || - value == DbType.Time) + if ((null == metatype) || (metatype.DbType != value)) { PropertyTypeChanging(); _metaType = MetaType.GetMetaTypeFromDbType(value); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs index 8fe10372b9..203fcc5a65 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs @@ -648,6 +648,17 @@ public void InferType_TimeSpan() Assert.Equal(DbType.Time, param.DbType); } + [Fact] + public void InferType_DateTimeOffset() + { + DateTimeOffset value = new DateTimeOffset(new DateTime(2019, 10, 15), new TimeSpan(1, 0, 0)); + + SqlParameter param = new SqlParameter(); + param.Value = value; + Assert.Equal(SqlDbType.DateTimeOffset, param.SqlDbType); + Assert.Equal(DbType.DateTimeOffset, param.DbType); + } + [Fact] public void LocaleId() { @@ -834,6 +845,48 @@ public void ResetDbType() Assert.Null(p.Value); } + [Theory] + [InlineData(DbType.AnsiString, SqlDbType.VarChar)] + [InlineData(DbType.AnsiStringFixedLength, SqlDbType.Char)] + [InlineData(DbType.Binary, SqlDbType.VarBinary)] + [InlineData(DbType.Boolean, SqlDbType.Bit)] + [InlineData(DbType.Byte, SqlDbType.TinyInt)] + [InlineData(DbType.Currency, SqlDbType.Money)] + [InlineData(DbType.Date, SqlDbType.Date)] + [InlineData(DbType.DateTime, SqlDbType.DateTime)] + [InlineData(DbType.DateTime2, SqlDbType.DateTime2)] + [InlineData(DbType.DateTimeOffset, SqlDbType.DateTimeOffset)] + [InlineData(DbType.Decimal, SqlDbType.Decimal)] + [InlineData(DbType.Double, SqlDbType.Float)] + [InlineData(DbType.Guid, SqlDbType.UniqueIdentifier)] + [InlineData(DbType.Int16, SqlDbType.SmallInt)] + [InlineData(DbType.Int32, SqlDbType.Int)] + [InlineData(DbType.Int64, SqlDbType.BigInt)] + [InlineData(DbType.Object, SqlDbType.Variant)] + [InlineData(DbType.Single, SqlDbType.Real)] + [InlineData(DbType.String, SqlDbType.NVarChar)] + [InlineData(DbType.Time, SqlDbType.Time)] + [InlineData(DbType.Xml, SqlDbType.Xml)] + public void Parameter_Supported(DbType dbType, SqlDbType sqlDbType) + { + var parameter = new SqlParameter(); + parameter.DbType = dbType; + Assert.Equal(dbType, parameter.DbType); + Assert.Equal(sqlDbType, parameter.SqlDbType); + } + + [Theory] + [InlineData(DbType.SByte)] + [InlineData(DbType.UInt16)] + [InlineData(DbType.UInt32)] + [InlineData(DbType.UInt64)] + [InlineData(DbType.VarNumeric)] + public void Parameter_NotSupported(DbType dbType) + { + var parameter = new SqlParameter(); + Assert.Throws(() => parameter.DbType = dbType); + } + [Fact] public void ResetSqlDbType() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs index d74525031f..294d949814 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DateTimeTest/DateTimeTest.cs @@ -4,6 +4,7 @@ using System; using System.Data; +using System.Data.Common; using System.Data.SqlTypes; using Xunit; @@ -239,9 +240,172 @@ public static void ReaderParameterTest() Assert.True(IsValidParam(SqlDbType.DateTimeOffset, "c4", dto.LocalDateTime, conn, tableName), "FAILED: Invalid param for DateTimeOffset SqlDbType"); Assert.False(IsValidParam(SqlDbType.DateTimeOffset, "c4", new TimeSpan(), conn, tableName), "FAILED: Invalid param for DateTimeOffset SqlDbType"); Assert.True(IsValidParam(SqlDbType.DateTimeOffset, "c4", "9999-12-31 23:59:59.997 +00:00", conn, tableName), "FAILED: Invalid param for DateTimeOffset SqlDbType"); - #endregion } + // Do the same thing as above except using DbType now + using (DbCommand cmd3 = conn.CreateCommand()) + { + cmd3.CommandText = tempTableInsert2; + DbParameter pi = cmd3.CreateParameter(); + pi.DbType = DbType.Int32; + pi.ParameterName = "@pi"; + DbParameter p0 = cmd3.CreateParameter(); + p0.DbType = DbType.DateTime; + p0.ParameterName = "@p0"; + DbParameter p1 = cmd3.CreateParameter(); + p1.DbType = DbType.Date; + p1.ParameterName = "@p1"; + DbParameter p2 = cmd3.CreateParameter(); + p2.DbType = DbType.Time; + p2.ParameterName = "@p2"; + DbParameter p3 = cmd3.CreateParameter(); + p3.DbType = DbType.DateTime2; + p3.ParameterName = "@p3"; + DbParameter p4 = cmd3.CreateParameter(); + p4.DbType = DbType.DateTimeOffset; + p4.ParameterName = "@p4"; + pi.Value = DBNull.Value; + p0.Value = DBNull.Value; + p1.Value = DBNull.Value; + p2.Value = DBNull.Value; + p3.Value = DBNull.Value; + p4.Value = DBNull.Value; + p3.Scale = 7; + + cmd3.Parameters.Add(pi); + cmd3.Parameters.Add(p0); + cmd3.Parameters.Add(p1); + cmd3.Parameters.Add(p2); + cmd3.Parameters.Add(p3); + cmd3.Parameters.Add(p4); + cmd3.ExecuteNonQuery(); + pi.Value = 1; + p0.Value = new DateTime(2000, 12, 31); + p1.Value = new DateTime(2000, 12, 31); + p2.Value = new TimeSpan(23, 59, 59); + p3.Value = new DateTime(2000, 12, 31); + p4.Value = new DateTimeOffset(2000, 12, 31, 23, 59, 59, new TimeSpan(-8, 0, 0)); + p3.Scale = 3; + + // This used to be broken for p2/TimeSpan before removing back-compat code for DbType.Date and DbType.Time parameters + cmd3.ExecuteNonQuery(); + + // Test 2 + cmd3.CommandText = "SELECT COUNT(*) FROM " + tableName + " WHERE @pi = ci AND @p0 = c0 AND @p1 = c1 AND @p2 = c2 AND @p3 = c3 AND @p4 = c4"; + pi.Value = 0; + p0.Value = new DateTime(1753, 1, 1, 0, 0, 0); + p1.Value = new DateTime(1753, 1, 1, 0, 0, 0); + p2.Value = new TimeSpan(0, 20, 12, 13, 360); + p3.Value = new DateTime(2000, 12, 31, 23, 59, 59, 997); + p4.Value = new DateTimeOffset(9999, 12, 31, 23, 59, 59, 997, TimeSpan.Zero); + p4.Scale = 3; + cmd3.Parameters.Clear(); + cmd3.Parameters.Add(pi); + cmd3.Parameters.Add(p0); + cmd3.Parameters.Add(p1); + cmd3.Parameters.Add(p2); + cmd3.Parameters.Add(p3); + cmd3.Parameters.Add(p4); + + // This used to be broken for p2/TimeSpan before removing back-compat code for DbType.Date and DbType.Time parameters + object scalarResult = cmd3.ExecuteScalar(); + Assert.True(scalarResult.Equals(1), string.Format("FAILED: Execute scalar returned unexpected result. Expected: {0}. Actual: {1}.", 1, scalarResult)); + + // Test 3 + + using (SqlCommand cmd4 = conn.CreateCommand()) + { + cmd4.CommandType = CommandType.StoredProcedure; + cmd4.CommandText = procName; + p0 = cmd3.CreateParameter(); + p0.DbType = DbType.DateTime; + p0.ParameterName = "@p0"; + cmd4.Parameters.Add(p0); + p1 = cmd3.CreateParameter(); + p1.DbType = DbType.Date; + p1.ParameterName = "@p1"; + cmd4.Parameters.Add(p1); + p2 = cmd3.CreateParameter(); + p2.DbType = DbType.Time; + p2.ParameterName = "@p2"; + cmd4.Parameters.Add(p2); + p3 = cmd3.CreateParameter(); + p3.DbType = DbType.DateTime2; + p3.ParameterName = "@p3"; + cmd4.Parameters.Add(p3); + p4 = cmd3.CreateParameter(); + p4.DbType = DbType.DateTimeOffset; + p4.ParameterName = "@p4"; + cmd4.Parameters.Add(p4); + p0.Direction = ParameterDirection.Output; + p1.Direction = ParameterDirection.Output; + p2.Direction = ParameterDirection.Output; + p3.Direction = ParameterDirection.Output; + p4.Direction = ParameterDirection.Output; + p2.Scale = 7; + cmd4.ExecuteNonQuery(); + + Assert.True(p0.Value.Equals((new SqlDateTime(1753, 1, 1, 0, 0, 0)).Value), "FAILED: SqlParameter p0 contained incorrect value"); + Assert.True(p1.Value.Equals(new DateTime(1753, 1, 1, 0, 0, 0)), "FAILED: SqlParameter p1 contained incorrect value"); + // This used to be broken for p2/TimeSpan before removing back-compat code for DbType.Date and DbType.Time parameters + Assert.True(p2.Value.Equals(new TimeSpan(0, 20, 12, 13, 360)), "FAILED: SqlParameter p2 contained incorrect value"); + Assert.True(p2.Scale.Equals(7), "FAILED: SqlParameter p0 contained incorrect scale"); + Assert.True(p3.Value.Equals(new DateTime(2000, 12, 31, 23, 59, 59, 997)), "FAILED: SqlParameter p3 contained incorrect value"); + Assert.True(p3.Scale.Equals(7), "FAILED: SqlParameter p3 contained incorrect scale"); + Assert.True(p4.Value.Equals(new DateTimeOffset(9999, 12, 31, 23, 59, 59, 997, TimeSpan.Zero)), "FAILED: SqlParameter p4 contained incorrect value"); + Assert.True(p4.Scale.Equals(7), "FAILED: SqlParameter p4 contained incorrect scale"); + + // Test 4 + cmd4.CommandText = procNullName; + cmd4.ExecuteNonQuery(); + } + Assert.True(p0.Value.Equals(DBNull.Value), "FAILED: SqlParameter p0 expected to be NULL"); + Assert.True(p1.Value.Equals(DBNull.Value), "FAILED: SqlParameter p1 expected to be NULL"); + Assert.True(p2.Value.Equals(DBNull.Value), "FAILED: SqlParameter p2 expected to be NULL"); + Assert.True(p3.Value.Equals(DBNull.Value), "FAILED: SqlParameter p3 expected to be NULL"); + Assert.True(p4.Value.Equals(DBNull.Value), "FAILED: SqlParameter p4 expected to be NULL"); + + // Date + Assert.False(IsValidParam(DbType.Date, "c1", new DateTimeOffset(1753, 1, 1, 0, 0, 0, TimeSpan.Zero), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.False(IsValidParam(DbType.Date, "c1", new SqlDateTime(1753, 1, 1, 0, 0, 0), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.True(IsValidParam(DbType.Date, "c1", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.True(IsValidParam(DbType.Date, "c1", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Utc), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.True(IsValidParam(DbType.Date, "c1", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Local), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.False(IsValidParam(DbType.Date, "c1", new TimeSpan(), conn, tableName), "FAILED: Invalid param for Date DbType"); + Assert.True(IsValidParam(DbType.Date, "c1", "1753-1-1", conn, tableName), "FAILED: Invalid param for Date DbType"); + + // Time + // These 7 Asserts used to be broken for before removing back-compat code for DbType.Date and DbType.Time parameters + Assert.False(IsValidParam(DbType.Time, "c2", new DateTimeOffset(1753, 1, 1, 0, 0, 0, TimeSpan.Zero), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.False(IsValidParam(DbType.Time, "c2", new SqlDateTime(1753, 1, 1, 0, 0, 0), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.False(IsValidParam(DbType.Time, "c2", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Unspecified), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.False(IsValidParam(DbType.Time, "c2", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Utc), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.False(IsValidParam(DbType.Time, "c2", new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Local), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.True(IsValidParam(DbType.Time, "c2", TimeSpan.Parse("20:12:13.36"), conn, tableName), "FAILED: Invalid param for Time DbType"); + Assert.True(IsValidParam(DbType.Time, "c2", "20:12:13.36", conn, tableName), "FAILED: Invalid param for Time DbType"); + + // DateTime2 + DateTime dt = DateTime.Parse("2000-12-31 23:59:59.997"); + Assert.False(IsValidParam(DbType.DateTime2, "c3", new DateTimeOffset(1753, 1, 1, 0, 0, 0, TimeSpan.Zero), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.False(IsValidParam(DbType.DateTime2, "c3", new SqlDateTime(2000, 12, 31, 23, 59, 59, 997), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.True(IsValidParam(DbType.DateTime2, "c3", new DateTime(2000, 12, 31, 23, 59, 59, 997, DateTimeKind.Unspecified), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.True(IsValidParam(DbType.DateTime2, "c3", new DateTime(2000, 12, 31, 23, 59, 59, 997, DateTimeKind.Utc), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.True(IsValidParam(DbType.DateTime2, "c3", new DateTime(2000, 12, 31, 23, 59, 59, 997, DateTimeKind.Local), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.False(IsValidParam(DbType.DateTime2, "c3", new TimeSpan(), conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + Assert.True(IsValidParam(DbType.DateTime2, "c3", "2000-12-31 23:59:59.997", conn, tableName), "FAILED: Invalid param for DateTime2 DbType"); + + // DateTimeOffset + DateTimeOffset dto = DateTimeOffset.Parse("9999-12-31 23:59:59.997 +00:00"); + Assert.True(IsValidParam(DbType.DateTimeOffset, "c4", dto, conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.False(IsValidParam(DbType.DateTimeOffset, "c4", new SqlDateTime(1753, 1, 1, 0, 0, 0), conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.True(IsValidParam(DbType.DateTimeOffset, "c4", new DateTime(9999, 12, 31, 15, 59, 59, 997, DateTimeKind.Unspecified), conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.True(IsValidParam(DbType.DateTimeOffset, "c4", dto.UtcDateTime, conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.True(IsValidParam(DbType.DateTimeOffset, "c4", dto.LocalDateTime, conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.False(IsValidParam(DbType.DateTimeOffset, "c4", new TimeSpan(), conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + Assert.True(IsValidParam(DbType.DateTimeOffset, "c4", "9999-12-31 23:59:59.997 +00:00", conn, tableName), "FAILED: Invalid param for DateTimeOffset DbType"); + } + #endregion + #region reader // Reader Tests cmd.CommandText = "SELECT * FROM " + tableName; @@ -443,6 +607,25 @@ private static bool IsValidParam(SqlDbType dbType, string col, object value, Sql return true; } + private static bool IsValidParam(DbType dbType, string col, object value, SqlConnection conn, string tempTable) + { + try + { + DbCommand cmd = new SqlCommand("SELECT COUNT(*) FROM " + tempTable + " WHERE " + col + " = @p", conn); + DbParameter p = cmd.CreateParameter(); + p.ParameterName = "@p"; + p.DbType = dbType; + p.Value = value; + cmd.Parameters.Add(p); + cmd.ExecuteScalar(); + } + catch (InvalidCastException) + { + return false; + } + return true; + } + private static bool IsString(SqlDataReader rdr, int column) { if (!rdr.IsDBNull(column))