Feature: Writing Execute Results to Temp File (#35)

* WIP for buffering in temporary file

* Adding support for writing to disk for buffering

* WIP - Adding file reader, factory for reader/writer

* Making long list use generics and implement IEnumerable

* Reading/Writing from file is working

* Removing unused 'skipValue' logic

* More tweaks to file buffer

Adding logic for cleaning up the temp files
Adding fix for empty/null column names

* Adding comments and cleanup

* Unit tests for FileStreamWrapper

* WIP adding more unit tests, and finishing up wiring up the output writers

* Finishing up initial unit tests

* Fixing bugs with long fields

* Squashed commit of the following:

commit df0ffc12a46cb286d801d08689964eac08ad71dd
Author: Benjamin Russell <beruss@microsoft.com>
Date:   Wed Sep 7 14:45:39 2016 -0700

    Removing last bit of async for file writing.

    We're seeing a 8x improvement of file write speeds!

commit 08a4b9f32e825512ca24d5dc03ef5acbf7cc6d94
Author: Benjamin Russell <beruss@microsoft.com>
Date:   Wed Sep 7 11:23:06 2016 -0700

    Removing async wrappers

* Rolling back test code for Program.cs

* Changes as per code review

* Fixing broken unit tests

* More fixes for codereview
This commit is contained in:
Benjamin Russell
2016-09-08 17:55:11 -07:00
committed by GitHub
parent 903eab61d1
commit 8aa3d524fc
24 changed files with 4050 additions and 195 deletions

View File

@@ -0,0 +1,226 @@
//
// 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.Collections.Generic;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Diagnostics;
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
{
/// <summary>
/// Wrapper around a DbColumn, which provides extra functionality, but can be used as a
/// regular DbColumn
/// </summary>
public class DbColumnWrapper : DbColumn
{
/// <summary>
/// All types supported by the server, stored as a hash set to provide O(1) lookup
/// </summary>
private static readonly HashSet<string> AllServerDataTypes = new HashSet<string>
{
"bigint",
"binary",
"bit",
"char",
"datetime",
"decimal",
"float",
"image",
"int",
"money",
"nchar",
"ntext",
"nvarchar",
"real",
"uniqueidentifier",
"smalldatetime",
"smallint",
"smallmoney",
"text",
"timestamp",
"tinyint",
"varbinary",
"varchar",
"sql_variant",
"xml",
"date",
"time",
"datetimeoffset",
"datetime2"
};
private readonly DbColumn internalColumn;
/// <summary>
/// Constructor for a DbColumnWrapper
/// </summary>
/// <remarks>Most of this logic is taken from SSMS ColumnInfo class</remarks>
/// <param name="column">The column we're wrapping around</param>
public DbColumnWrapper(DbColumn column)
{
internalColumn = column;
switch (column.DataTypeName)
{
case "varchar":
case "nvarchar":
IsChars = true;
Debug.Assert(column.ColumnSize.HasValue);
if (column.ColumnSize.Value == int.MaxValue)
{
//For Yukon, special case nvarchar(max) with column name == "Microsoft SQL Server 2005 XML Showplan" -
//assume it is an XML showplan.
//Please note this field must be in sync with a similar field defined in QESQLBatch.cs.
//This is not the best fix that we could do but we are trying to minimize code impact
//at this point. Post Yukon we should review this code again and avoid
//hard-coding special column name in multiple places.
const string YukonXmlShowPlanColumn = "Microsoft SQL Server 2005 XML Showplan";
if (column.ColumnName == YukonXmlShowPlanColumn)
{
// Indicate that this is xml to apply the right size limit
// Note we leave chars type as well to use the right retrieval mechanism.
IsXml = true;
}
IsLong = true;
}
break;
case "text":
case "ntext":
IsChars = true;
IsLong = true;
break;
case "xml":
IsXml = true;
IsLong = true;
break;
case "binary":
case "image":
IsBytes = true;
IsLong = true;
break;
case "varbinary":
case "rowversion":
IsBytes = true;
Debug.Assert(column.ColumnSize.HasValue);
if (column.ColumnSize.Value == int.MaxValue)
{
IsLong = true;
}
break;
case "sql_variant":
IsSqlVariant = true;
break;
default:
if (!AllServerDataTypes.Contains(column.DataTypeName))
{
// treat all UDT's as long/bytes data types to prevent the CLR from attempting
// to load the UDT assembly into our process to call ToString() on the object.
IsUdt = true;
IsBytes = true;
IsLong = true;
}
break;
}
if (IsUdt)
{
// udtassemblyqualifiedname property is used to find if the datatype is of hierarchyid assembly type
// Internally hiearchyid is sqlbinary so providerspecific type and type is changed to sqlbinarytype
object assemblyQualifiedName = internalColumn.UdtAssemblyQualifiedName;
const string hierarchyId = "MICROSOFT.SQLSERVER.TYPES.SQLHIERARCHYID";
if (assemblyQualifiedName != null &&
string.Equals(assemblyQualifiedName.ToString(), hierarchyId, StringComparison.OrdinalIgnoreCase))
{
DataType = typeof(SqlBinary);
}
else
{
DataType = typeof(byte[]);
}
}
else
{
DataType = DataType;
}
}
#region Properties
/// <summary>
/// Whether or not the column is bytes
/// </summary>
public bool IsBytes { get; private set; }
/// <summary>
/// Whether or not the column is a character type
/// </summary>
public bool IsChars { get; private set; }
/// <summary>
/// Whether or not the column is a long type (eg, varchar(MAX))
/// </summary>
public new bool IsLong { get; private set; }
/// <summary>
/// Whether or not the column is a SqlVariant type
/// </summary>
public bool IsSqlVariant { get; private set; }
/// <summary>
/// Whether or not the column is a user-defined type
/// </summary>
public bool IsUdt { get; private set; }
/// <summary>
/// Whether or not the column is XML
/// </summary>
public bool IsXml { get; private set; }
#endregion
#region DbColumn Fields
/// <summary>
/// Override for column name, if null or empty, we default to a "no column name" value
/// </summary>
public new string ColumnName
{
get
{
// TODO: Localize
return string.IsNullOrEmpty(internalColumn.ColumnName) ? "(No column name)" : internalColumn.ColumnName;
}
}
public new bool? AllowDBNull { get { return internalColumn.AllowDBNull; } }
public new string BaseCatalogName { get { return internalColumn.BaseCatalogName; } }
public new string BaseColumnName { get { return internalColumn.BaseColumnName; } }
public new string BaseServerName { get { return internalColumn.BaseServerName; } }
public new string BaseTableName { get { return internalColumn.BaseTableName; } }
public new int? ColumnOrdinal { get { return internalColumn.ColumnOrdinal; } }
public new int? ColumnSize { get { return internalColumn.ColumnSize; } }
public new bool? IsAliased { get { return internalColumn.IsAliased; } }
public new bool? IsAutoIncrement { get { return internalColumn.IsAutoIncrement; } }
public new bool? IsExpression { get { return internalColumn.IsExpression; } }
public new bool? IsHidden { get { return internalColumn.IsHidden; } }
public new bool? IsIdentity { get { return internalColumn.IsIdentity; } }
public new bool? IsKey { get { return internalColumn.IsKey; } }
public new bool? IsReadOnly { get { return internalColumn.IsReadOnly; } }
public new bool? IsUnique { get { return internalColumn.IsUnique; } }
public new int? NumericPrecision { get { return internalColumn.NumericPrecision; } }
public new int? NumericScale { get { return internalColumn.NumericScale; } }
public new string UdtAssemblyQualifiedName { get { return internalColumn.UdtAssemblyQualifiedName; } }
public new Type DataType { get; private set; }
public new string DataTypeName { get { return internalColumn.DataTypeName; } }
#endregion
}
}