diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/IFileStreamWriter.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/IFileStreamWriter.cs index 7cfffee8..951bd89c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/IFileStreamWriter.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/IFileStreamWriter.cs @@ -5,6 +5,7 @@ using System; using System.Data.SqlTypes; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { @@ -25,7 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage int WriteDouble(double val); int WriteDecimal(decimal val); int WriteSqlDecimal(SqlDecimal val); - int WriteDateTime(DateTime val); + int WriteDateTime(DbColumnWrapper column, DateTime val); int WriteDateTimeOffset(DateTimeOffset dtoVal); int WriteTimeSpan(TimeSpan val); int WriteString(string val); diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs index 5df33596..487f827b 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamReader.cs @@ -260,11 +260,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// A DateTime public FileStreamReadResult ReadDateTime(long offset) { - return ReadCellHelper(offset, length => - { - long ticks = BitConverter.ToInt64(buffer, 0); - return new DateTime(ticks); - }); + int precision = 0; + + return ReadCellHelper(offset, + length => + { + precision = BitConverter.ToInt32(buffer, 0); + long ticks = BitConverter.ToInt64(buffer, 4); + return new DateTime(ticks); + }, null, + time => + { + string format = "yyyy-MM-dd HH:mm:ss"; + if (precision > 0) + { + // Output the number milliseconds equivalent to the precision + // NOTE: string('f', precision) will output ffff for precision=4 + format += "." + new string('f', precision); + } + return time.ToString(format); + }); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs index bb7167e9..75045aff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/DataStorage/ServiceBufferFileStreamWriter.cs @@ -38,7 +38,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage /// /// Functions to use for writing various types to a file /// - private readonly Dictionary> writeMethods; + private readonly Dictionary> writeMethods; #endregion @@ -74,37 +74,78 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage this.maxXmlCharsToStore = maxXmlCharsToStore; // Define what methods to use to write a type to the file - writeMethods = new Dictionary> + writeMethods = new Dictionary> { - {typeof(string), val => WriteString((string) val)}, - {typeof(short), val => WriteInt16((short) val)}, - {typeof(int), val => WriteInt32((int) val)}, - {typeof(long), val => WriteInt64((long) val)}, - {typeof(byte), val => WriteByte((byte) val)}, - {typeof(char), val => WriteChar((char) val)}, - {typeof(bool), val => WriteBoolean((bool) val)}, - {typeof(double), val => WriteDouble((double) val) }, - {typeof(float), val => WriteSingle((float) val) }, - {typeof(decimal), val => WriteDecimal((decimal) val) }, - {typeof(DateTime), val => WriteDateTime((DateTime) val) }, - {typeof(DateTimeOffset), val => WriteDateTimeOffset((DateTimeOffset) val) }, - {typeof(TimeSpan), val => WriteTimeSpan((TimeSpan) val) }, - {typeof(byte[]), val => WriteBytes((byte[]) val)}, + {typeof(string), (val, col) => WriteString((string) val)}, + {typeof(short), (val, col) => WriteInt16((short) val)}, + {typeof(int), (val, col) => WriteInt32((int) val)}, + {typeof(long), (val, col) => WriteInt64((long) val)}, + {typeof(byte), (val, col) => WriteByte((byte) val)}, + {typeof(char), (val, col) => WriteChar((char) val)}, + {typeof(bool), (val, col) => WriteBoolean((bool) val)}, + {typeof(double), (val, col) => WriteDouble((double) val) }, + {typeof(float), (val, col) => WriteSingle((float) val) }, + {typeof(decimal), (val, col) => WriteDecimal((decimal) val) }, + {typeof(DateTime), (val, col) => WriteDateTime(col, (DateTime) val) }, + {typeof(DateTimeOffset), (val, col) => WriteDateTimeOffset((DateTimeOffset) val) }, + {typeof(TimeSpan), (val, col) => WriteTimeSpan((TimeSpan) val) }, + {typeof(byte[]), (val, col) => WriteBytes((byte[]) 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(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(SqlMoney), val => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj)) } + { + typeof(SqlString), + (val, col) => WriteNullable((SqlString) val, obj => WriteString((string) obj)) + }, + { + typeof(SqlInt16), + (val, col) => WriteNullable((SqlInt16) val, obj => WriteInt16((short) obj)) + }, + { + typeof(SqlInt32), + (val, col) => WriteNullable((SqlInt32) val, obj => WriteInt32((int) obj)) + }, + { + typeof(SqlInt64), + (val, col) => WriteNullable((SqlInt64) val, obj => WriteInt64((long) obj)) + }, + { + typeof(SqlByte), + (val, col) => WriteNullable((SqlByte) val, obj => WriteByte((byte) obj)) + }, + { + typeof(SqlBoolean), + (val, col) => WriteNullable((SqlBoolean) val, obj => WriteBoolean((bool) obj)) }, + { + typeof(SqlDouble), + (val, col) => WriteNullable((SqlDouble) val, obj => WriteDouble((double) obj)) + }, + { + typeof(SqlSingle), + (val, col) => WriteNullable((SqlSingle) val, obj => WriteSingle((float) obj)) + }, + { + typeof(SqlDecimal), + (val, col) => WriteNullable((SqlDecimal) val, obj => WriteSqlDecimal((SqlDecimal) obj)) + }, + { + typeof(SqlDateTime), + (val, col) => WriteNullable((SqlDateTime) val, obj => WriteDateTime(col, (DateTime) obj)) + }, + { + typeof(SqlBytes), + (val, col) => WriteNullable((SqlBytes) val, obj => WriteBytes((byte[]) obj)) + }, + { + typeof(SqlBinary), + (val, col) => WriteNullable((SqlBinary) val, obj => WriteBytes((byte[]) obj)) + }, + { + typeof(SqlGuid), + (val, col) => WriteNullable((SqlGuid) val, obj => WriteGuid((Guid) obj)) + }, + { + typeof(SqlMoney), + (val, col) => WriteNullable((SqlMoney) val, obj => WriteMoney((SqlMoney) obj)) + } }; } @@ -190,10 +231,10 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage } // Use the appropriate writing method for the type - Func writeMethod; + Func writeMethod; if (writeMethods.TryGetValue(tVal, out writeMethod)) { - rowBytes += writeMethod(values[i]); + rowBytes += writeMethod(values[i], ci); } else { @@ -354,12 +395,25 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage } /// - /// Writes a DateTime to the file + /// Writes a DateTime to the file as precision and ticks /// /// Number of bytes used to store the DateTime - public int WriteDateTime(DateTime dtVal) + public int WriteDateTime(DbColumnWrapper col, DateTime dtVal) { - return WriteInt64(dtVal.Ticks); + // Length + var length = WriteLength(12); + + // Precision + intBuffer[0] = col.NumericScale ?? 3; + Buffer.BlockCopy(intBuffer, 0, byteBuffer, 0, 4); + + // Ticks + longBuffer[0] = dtVal.Ticks; + Buffer.BlockCopy(longBuffer, 0, byteBuffer, 4, 8); + + length += WriteHelper(byteBuffer, 12); + + return length; } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs index b454eafb..40496d63 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs @@ -8,7 +8,9 @@ using System.Collections.Generic; using System.Data.SqlTypes; using System.IO; using System.Text; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; +using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Moq; using Xunit; @@ -229,18 +231,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage } } - [Fact] - public void DateTimeTest() + [Theory] + [InlineData(3)] // Scale 3 = DATETIME + [InlineData(7)] // Scale 7 = DATETIME2 + public void DateTimeTest(int scale) { - // Setup: Create some test values + // Setup: Create some test values and a column with scale set // NOTE: We are doing these here instead of InlineData because DateTime values can't be written as constant expressions + DbColumnWrapper col = new DbColumnWrapper(new TestDbColumn("dbcol", scale)); DateTime[] testValues = { DateTime.Now, DateTime.UtcNow, DateTime.MinValue, DateTime.MaxValue }; foreach (DateTime value in testValues) { - VerifyReadWrite(sizeof(long) + 1, value, (writer, val) => writer.WriteDateTime(val), reader => reader.ReadDateTime(0)); + VerifyReadWrite(sizeof(long) + sizeof(int) + 1, value, (writer, val) => writer.WriteDateTime(col, val), reader => reader.ReadDateTime(0)); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs index b8b93276..00e88637 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs @@ -17,5 +17,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility base.DataType = typeof(string); base.DataTypeName = "nvarchar"; } + + public TestDbColumn(string columnName, int numericScale) + : this(columnName) + { + base.NumericScale = numericScale; + } } }