diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
index 5cb28ee4..684f4cea 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs
@@ -8631,6 +8631,11 @@ namespace Microsoft.SqlTools.ServiceLayer
return Keys.GetString(Keys.QueryServiceQueryFailed, message);
}
+ public static string QueryServiceUnsupportedSqlVariantType(string underlyingType, string columnName)
+ {
+ return Keys.GetString(Keys.QueryServiceUnsupportedSqlVariantType, underlyingType, columnName);
+ }
+
public static string QueryServiceSaveAsFail(string fileName, string message)
{
return Keys.GetString(Keys.QueryServiceSaveAsFail, fileName, message);
@@ -9011,6 +9016,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string QueryServiceResultSetTooLarge = "QueryServiceResultSetTooLarge";
+ public const string QueryServiceUnsupportedSqlVariantType = "QueryServiceUnsupportedSqlVariantType";
+
+
public const string QueryServiceSaveAsResultSetNotComplete = "QueryServiceSaveAsResultSetNotComplete";
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
index a2e5b6db..74435378 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx
@@ -303,6 +303,11 @@
Result set has too many rows to be safely loaded
+
+ The underlying type "{0}" for sql variant column "{1}" could not be resolved.
+ .
+ Parameters: 0 - underlyingType (string), 1 - columnName (string)
+
Result cannot be saved until query execution has completed
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
index 72c0a886..60d6183d 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
+++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings
@@ -124,6 +124,8 @@ QueryServiceResultSetHasNoResults = Query has no results to return
QueryServiceResultSetTooLarge = Result set has too many rows to be safely loaded
+QueryServiceUnsupportedSqlVariantType(string underlyingType, string columnName) = The underlying type "{0}" for sql variant column "{1}" could not be resolved.
+
### Save As Requests
QueryServiceSaveAsResultSetNotComplete = Result cannot be saved until query execution has completed
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs
index de22f564..e46e630e 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
+using System.Data;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
@@ -40,6 +41,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
private readonly Dictionary readMethods;
+ private readonly Dictionary sqlDBTypeMap;
+
#endregion
///
@@ -98,6 +101,37 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{typeof(SqlGuid), (o, id, col) => ReadGuid(o, id)},
{typeof(SqlMoney), (o, id, col) => ReadMoney(o, id)},
};
+
+ sqlDBTypeMap = new Dictionary {
+ {SqlDbType.BigInt, typeof(SqlInt64)},
+ {SqlDbType.Binary, typeof(SqlBinary)},
+ {SqlDbType.Bit, typeof(SqlBoolean)},
+ {SqlDbType.Char, typeof(SqlString)},
+ {SqlDbType.Date, typeof(SqlDateTime)},
+ {SqlDbType.DateTime, typeof(SqlDateTime)},
+ {SqlDbType.DateTime2, typeof(SqlDateTime)},
+ {SqlDbType.DateTimeOffset, typeof(DateTimeOffset)},
+ {SqlDbType.Decimal, typeof(SqlDecimal)},
+ {SqlDbType.Float, typeof(SqlDouble)},
+ {SqlDbType.Image, typeof(SqlBinary)},
+ {SqlDbType.Int, typeof(SqlInt32)},
+ {SqlDbType.Money, typeof(SqlMoney)},
+ {SqlDbType.NChar, typeof(SqlString)},
+ {SqlDbType.NText, typeof(SqlString)},
+ {SqlDbType.NVarChar, typeof(SqlString)},
+ {SqlDbType.Real, typeof(SqlSingle)},
+ {SqlDbType.SmallDateTime, typeof(SqlDateTime)},
+ {SqlDbType.SmallInt, typeof(SqlInt16)},
+ {SqlDbType.SmallMoney, typeof(SqlMoney)},
+ {SqlDbType.Text, typeof(SqlString)},
+ {SqlDbType.Time, typeof(TimeSpan)},
+ {SqlDbType.Timestamp, typeof(SqlBinary)},
+ {SqlDbType.TinyInt, typeof(SqlByte)},
+ {SqlDbType.UniqueIdentifier, typeof(SqlGuid)},
+ {SqlDbType.VarBinary, typeof(SqlBinary)},
+ {SqlDbType.VarChar, typeof(SqlString)},
+ {SqlDbType.Xml, typeof(SqlString)}
+ };
}
#region IFileStreamStorage Implementation
@@ -134,19 +168,25 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
continue;
}
- // The typename is stored in the string
- colType = Type.GetType(sqlVariantType);
-
- // Workaround .NET bug, see sqlbu# 440643 and vswhidbey# 599834
- // TODO: Is this workaround necessary for .NET Core?
- if (colType == null && sqlVariantType == "System.Data.SqlTypes.SqlSingle")
+ // We need to specify the assembly name for SQL types in order to resolve the type correctly.
+ if (sqlVariantType.StartsWith("System.Data.SqlTypes."))
{
- colType = typeof(SqlSingle);
+ sqlVariantType = sqlVariantType + ", System.Data.Common";
+ }
+ colType = Type.GetType(sqlVariantType);
+ if (colType == null)
+ {
+ throw new ArgumentException(SR.QueryServiceUnsupportedSqlVariantType(sqlVariantType, column.ColumnName));
}
}
else
{
- colType = column.DataType;
+ Type type;
+ if (!sqlDBTypeMap.TryGetValue(column.SqlDbType, out type))
+ {
+ type = typeof(SqlString);
+ }
+ colType = type;
}
// Use the right read function for the type to read the data from the file
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs
index 522bdd55..3267b6c9 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs
@@ -92,19 +92,19 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
{typeof(byte[]), val => WriteBytes((byte[]) val)},
{typeof(Guid), val => WriteGuid((Guid) val)},
- {typeof(SqlString), val => WriteNullable((SqlString) val, obj => WriteString((string) obj))},
- {typeof(SqlInt16), val => WriteNullable((SqlInt16) val, obj => WriteInt16((short) obj))},
- {typeof(SqlInt32), val => WriteNullable((SqlInt32) val, obj => WriteInt32((int) obj))},
- {typeof(SqlInt64), val => WriteNullable((SqlInt64) val, obj => WriteInt64((long) obj)) },
- {typeof(SqlByte), val => WriteNullable((SqlByte) val, obj => WriteByte((byte) obj)) },
- {typeof(SqlBoolean), val => WriteNullable((SqlBoolean) val, obj => WriteBoolean((bool) obj)) },
- {typeof(SqlDouble), val => WriteNullable((SqlDouble) val, obj => WriteDouble((double) obj)) },
- {typeof(SqlSingle), val => WriteNullable((SqlSingle) val, obj => WriteSingle((float) obj)) },
+ {typeof(SqlString), val => WriteNullable((SqlString) val, obj => WriteString(((SqlString) obj).Value))},
+ {typeof(SqlInt16), val => WriteNullable((SqlInt16) val, obj => WriteInt16(((SqlInt16) obj).Value))},
+ {typeof(SqlInt32), val => WriteNullable((SqlInt32) val, obj => WriteInt32(((SqlInt32)obj).Value))},
+ {typeof(SqlInt64), val => WriteNullable((SqlInt64) val, obj => WriteInt64(((SqlInt64) obj).Value)) },
+ {typeof(SqlByte), val => WriteNullable((SqlByte) val, obj => WriteByte(((SqlByte) obj).Value)) },
+ {typeof(SqlBoolean), val => WriteNullable((SqlBoolean) val, obj => WriteBoolean(((SqlBoolean) obj).Value)) },
+ {typeof(SqlDouble), val => WriteNullable((SqlDouble) val, obj => WriteDouble(((SqlDouble) obj).Value)) },
+ {typeof(SqlSingle), val => WriteNullable((SqlSingle) val, obj => WriteSingle(((SqlSingle) obj).Value)) },
{typeof(SqlDecimal), val => WriteNullable((SqlDecimal) val, obj => WriteSqlDecimal((SqlDecimal) obj)) },
- {typeof(SqlDateTime), val => WriteNullable((SqlDateTime) val, obj => WriteDateTime((DateTime) obj)) },
- {typeof(SqlBytes), val => WriteNullable((SqlBytes) val, obj => WriteBytes((byte[]) obj)) },
- {typeof(SqlBinary), val => WriteNullable((SqlBinary) val, obj => WriteBytes((byte[]) obj)) },
- {typeof(SqlGuid), val => WriteNullable((SqlGuid) val, obj => WriteGuid((Guid) obj)) },
+ {typeof(SqlDateTime), val => WriteNullable((SqlDateTime) val, obj => WriteDateTime(((SqlDateTime) obj).Value)) },
+ {typeof(SqlBytes), val => WriteNullable((SqlBytes) val, obj => WriteBytes(((SqlBytes) obj).Value)) },
+ {typeof(SqlBinary), val => WriteNullable((SqlBinary) val, obj => WriteBytes(((SqlBinary) obj).Value)) },
+ {typeof(SqlGuid), val => WriteNullable((SqlGuid) val, obj => WriteGuid(((SqlGuid) obj).Value)) },
{typeof(SqlMoney), val => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj)) }
};
}
@@ -299,7 +299,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
internal int WriteBoolean(bool val)
{
byteBuffer[0] = 0x01; // length
- byteBuffer[1] = (byte) (val ? 0x01 : 0x00);
+ byteBuffer[1] = (byte)(val ? 0x01 : 0x00);
return FileUtilities.WriteWithLength(fileStream, byteBuffer, 2);
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/StorageDataReader.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/StorageDataReader.cs
index 68742616..560e3209 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/StorageDataReader.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/StorageDataReader.cs
@@ -98,7 +98,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
/// The value of the given column
public object GetValue(int i)
{
- return sqlDataReader == null ? DbDataReader.GetValue(i) : sqlDataReader.GetValue(i);
+ return sqlDataReader == null ? DbDataReader.GetValue(i) : sqlDataReader.GetSqlValue(i);
}
///
@@ -113,7 +113,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
}
else
{
- sqlDataReader.GetValues(values);
+ sqlDataReader.GetSqlValues(values);
}
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataStorage/StorageDataReaderTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataStorage/StorageDataReaderTests.cs
index f5b35462..e65fa5e7 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataStorage/StorageDataReaderTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataStorage/StorageDataReaderTests.cs
@@ -44,6 +44,20 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryExecution.DataSt
Assert.NotNull(bytes);
}
+ ///
+ /// Validate GetBytesWithMaxCapacity
+ ///
+ [Test]
+ public void GetLongDecimalTest()
+ {
+ // SQL Server support up to 38 digits of decimal
+ var storageReader = GetTestStorageDataReader(
+ "SELECT 99999999999999999999999999999999999999");
+ storageReader.DbDataReader.Read();
+ var value = storageReader.GetValue(0);
+ Assert.AreEqual("99999999999999999999999999999999999999", value.ToString());
+ }
+
///
/// Validate GetCharsWithMaxCapacity
///
@@ -63,7 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryExecution.DataSt
Assert.True(shortName.Length == 2);
Assert.Throws(() => storageReader.GetBytesWithMaxCapacity(0, 0));
- Assert.Throws(() => storageReader.GetCharsWithMaxCapacity(0, 0));
+ Assert.Throws(() => storageReader.GetCharsWithMaxCapacity(0, 0));
}
///
@@ -94,10 +108,10 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryExecution.DataSt
writer.Write(output);
Assert.True(writer.ToString().Equals(output));
writer.Write('.');
- Assert.True(writer.ToString().Equals(output + '.'));
+ Assert.True(writer.ToString().Equals(output + '.'));
writer.Write(output);
writer.Write('.');
Assert.True(writer.ToString().Equals(output + '.'));
- }
+ }
}
}
\ No newline at end of file
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataTypeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataTypeTests.cs
new file mode 100644
index 00000000..955be2ca
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/QueryExecution/DataTypeTests.cs
@@ -0,0 +1,209 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.SqlTools.ServiceLayer.Connection;
+using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
+using Microsoft.SqlTools.ServiceLayer.QueryExecution;
+using Microsoft.SqlTools.ServiceLayer.SqlContext;
+using Microsoft.SqlTools.ServiceLayer.Test.Common;
+using NUnit.Framework;
+
+namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.QueryExecution
+{
+ public class DataTypeTests
+ {
+ [Test]
+ public async Task BigIntTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100 AS BIGINT)", "100");
+ }
+
+ [Test]
+ public async Task SqlVariantTest()
+ {
+ await ExecuteAndVerifyResult("DECLARE @ID sql_variant = 90;select @ID", "90");
+ }
+
+ [Test]
+ public async Task BinaryTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100 AS BINARY)", "0x000000000000000000000000000000000000000000000000000000000064");
+ }
+
+ [Test]
+ public async Task BitTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(0 AS BIT)", "0");
+ await ExecuteAndVerifyResult("SELECT CAST(1 AS BIT)", "1");
+ }
+
+ [Test]
+ public async Task CharTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('A' AS CHAR(1))", "A");
+ }
+
+ [Test]
+ public async Task DateTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('2020-01-01' AS DATE)", "2020-01-01");
+ }
+
+ [Test]
+ public async Task DateTimeTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('2020-01-01' AS DATETIME)", "2020-01-01 00:00:00.000");
+ }
+
+ [Test]
+ public async Task DateTime2Test()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('2020-01-01' AS DATETIME2)", "2020-01-01 00:00:00.0000000");
+ }
+
+ [Test]
+ public async Task DateTimeOffsetTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('2020-01-01' AS DATETIMEOFFSET)", "2020-01-01 00:00:00.0000000 +00:00");
+ }
+
+ [Test]
+ public async Task DecimalTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(99999999999999999999999999999999999999 AS DECIMAL(38))", "99999999999999999999999999999999999999");
+ }
+
+ [Test]
+ public async Task FloatTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100.11 AS FLOAT)", "100.11");
+ }
+
+ [Test]
+ public async Task ImageTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('0' AS IMAGE)", "0x30");
+ }
+
+ [Test]
+ public async Task IntTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('100' AS INT)", "100");
+ }
+
+ [Test]
+ public async Task MoneyTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('100' AS MONEY)", "100.00");
+ }
+
+ [Test]
+ public async Task NCharTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(N'测' AS NCHAR(1))", "测");
+ }
+
+ [Test]
+ public async Task NTextTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(N'测试' AS NTEXT)", "测试");
+ }
+
+ [Test]
+ public async Task NVarCharTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(N'测试' AS NVARCHAR)", "测试");
+ }
+
+ [Test]
+ public async Task RealTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100 AS REAL)", "100");
+ }
+
+ [Test]
+ public async Task SmallDateTimeTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('2021-01-01' AS SMALLDATETIME)", "2021-01-01 00:00:00");
+ }
+
+ [Test]
+ public async Task SmallIntTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100 AS SMALLINT)", "100");
+ }
+
+ [Test]
+ public async Task SmallMoneyTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(100 AS SMALLMONEY)", "100.00");
+ }
+
+ [Test]
+ public async Task TextTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('abc' AS TEXT)", "abc");
+ }
+
+ [Test]
+ public async Task TimeTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('1:00:00.001' AS TIME)", "01:00:00.0010000");
+ }
+
+ [Test]
+ public async Task TimestampTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('A' AS TIMESTAMP)", "0x4100000000000000");
+ }
+
+ [Test]
+ public async Task TinyIntTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST(1 AS TINYINT)", "1");
+ }
+
+ [Test]
+ public async Task UniqueIdentifierTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('fbf31d5b-bda2-4907-a50c-5458e95248ae' AS UNIQUEIDENTIFIER)", "fbf31d5b-bda2-4907-a50c-5458e95248ae");
+ }
+
+ [Test]
+ public async Task VarBinaryTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('ABCD' AS VARBINARY)", "0x41424344");
+ }
+
+ [Test]
+ public async Task VarCharTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('ABCD' AS VARCHAR)", "ABCD");
+ }
+
+ [Test]
+ public async Task XmlTest()
+ {
+ await ExecuteAndVerifyResult("SELECT CAST('1234' AS XML)", "1234");
+ }
+
+ private async Task ExecuteAndVerifyResult(string queryText, string expectedValue)
+ {
+ // Given a connection to a live database
+ var result = LiveConnectionHelper.InitLiveConnectionInfo();
+ ConnectionInfo connInfo = result.ConnectionInfo;
+ var fileStreamFactory = MemoryFileSystem.GetFileStreamFactory();
+ Query query = new Query(queryText, connInfo, new QueryExecutionSettings(), fileStreamFactory);
+ query.Execute();
+ query.ExecutionTask.Wait();
+ var subset = await query.GetSubset(0, 0, 0, 10);
+ Assert.AreEqual(1, subset.RowCount, "Row count does not match expected value.");
+ var actualValue = subset.Rows[0][0].DisplayValue;
+ Assert.AreEqual(expectedValue, actualValue);
+ }
+ }
+}
\ No newline at end of file