Edit Data: Better Formatting Errors (#562)

* Refactoring sql script formatting helpers into To and From helpers

* Updates to make error messages for formatting errors more useful

* Fixing dumb breaks in unit tests

* Addressing comments from PR

* Updates to the SR files...
This commit is contained in:
Benjamin Russell
2017-12-05 17:00:13 -08:00
committed by GitHub
parent e7b76a6dec
commit 64133d929e
37 changed files with 7869 additions and 17711 deletions

View File

@@ -15,7 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement; using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData namespace Microsoft.SqlTools.ServiceLayer.EditData
@@ -440,12 +440,9 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
public static string[] GetEditTargetName(EditInitializeParams initParams) public static string[] GetEditTargetName(EditInitializeParams initParams)
{ {
// Step 1) Look up the SMO metadata return initParams.SchemaName != null
if (initParams.SchemaName != null) ? new [] { initParams.SchemaName, initParams.ObjectName }
{ : FromSqlScript.DecodeMultipartIdentifier(initParams.ObjectName);
return new [] { initParams.SchemaName, initParams.ObjectName };
}
return SqlScriptFormatter.DecodeMultipartIdenfitier(initParams.ObjectName);
} }
private async Task CommitEditsInternal(DbConnection connection, Func<Task> successHandler, Func<Exception, Task> errorHandler) private async Task CommitEditsInternal(DbConnection connection, Func<Task> successHandler, Func<Exception, Task> errorHandler)

View File

@@ -10,7 +10,7 @@ using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData namespace Microsoft.SqlTools.ServiceLayer.EditData
@@ -92,12 +92,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
// The default value may be escaped // The default value may be escaped
string defaultValue = smoColumn.DefaultConstraint == null string defaultValue = smoColumn.DefaultConstraint == null
? null ? null
: SqlScriptFormatter.UnwrapLiteral(smoColumn.DefaultConstraint.Text); : FromSqlScript.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
EditColumnMetadata column = new EditColumnMetadata EditColumnMetadata column = new EditColumnMetadata
{ {
DefaultValue = defaultValue, DefaultValue = defaultValue,
EscapedName = SqlScriptFormatter.FormatIdentifier(smoColumn.Name), EscapedName = ToSqlScript.FormatIdentifier(smoColumn.Name),
Ordinal = i, Ordinal = i,
}; };
editColumns.Add(column); editColumns.Add(column);
@@ -114,13 +114,13 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
// Escape the parts of the name // Escape the parts of the name
string[] objectNameParts = {smoResult.Schema, smoResult.Name}; string[] objectNameParts = {smoResult.Schema, smoResult.Name};
string escapedMultipartName = SqlScriptFormatter.FormatMultipartIdentifier(objectNameParts); string escapedMultipartName = ToSqlScript.FormatMultipartIdentifier(objectNameParts);
return new EditTableMetadata return new EditTableMetadata
{ {
Columns = editColumns.ToArray(), Columns = editColumns.ToArray(),
EscapedMultipartName = escapedMultipartName, EscapedMultipartName = escapedMultipartName,
IsMemoryOptimized = isMemoryOptimized, IsMemoryOptimized = isMemoryOptimized
}; };
} }
} }

View File

@@ -8,6 +8,7 @@ using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
@@ -34,50 +35,60 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
Validate.IsNotNull(nameof(valueAsString), valueAsString); Validate.IsNotNull(nameof(valueAsString), valueAsString);
// Store the state that won't be changed // Store the state that won't be changed
Column = column; try
Type columnType = column.DataType; {
Column = column;
Type columnType = column.DataType;
// Check for null // Check for null
if (valueAsString == NullString) if (valueAsString == NullString)
{ {
ProcessNullValue(); ProcessNullValue();
}
else if (columnType == typeof(byte[]))
{
// Binary columns need special attention
ProcessBinaryCell(valueAsString);
}
else if (columnType == typeof(string))
{
ProcessTextCell(valueAsString);
}
else if (columnType == typeof(Guid))
{
Value = Guid.Parse(valueAsString);
ValueAsString = Value.ToString();
}
else if (columnType == typeof(TimeSpan))
{
ProcessTimespanColumn(valueAsString);
}
else if (columnType == typeof(DateTimeOffset))
{
Value = DateTimeOffset.Parse(valueAsString, CultureInfo.CurrentCulture);
ValueAsString = Value.ToString();
}
else if (columnType == typeof(bool))
{
ProcessBooleanCell(valueAsString);
}
// @TODO: Microsoft.SqlServer.Types.SqlHierarchyId
else
{
// Attempt to go straight to the destination type, if we know what it is, otherwise
// leave it as a string
Value = columnType != null
? Convert.ChangeType(valueAsString, columnType, CultureInfo.CurrentCulture)
: valueAsString;
ValueAsString = Value.ToString();
}
} }
else if (columnType == typeof(byte[])) catch (FormatException fe)
{ {
// Binary columns need special attention // Pretty up the exception so the user can learn a bit from it
ProcessBinaryCell(valueAsString); // NOTE: Other formatting errors raised by helpers are InvalidOperationException to
} // avoid being prettied here
else if (columnType == typeof(string)) throw new FormatException(SR.EditDataInvalidFormat(column.ColumnName, ToSqlScript.FormatColumnType(column)), fe);
{
ProcessTextCell(valueAsString);
}
else if (columnType == typeof(Guid))
{
Value = Guid.Parse(valueAsString);
ValueAsString = Value.ToString();
}
else if (columnType == typeof(TimeSpan))
{
ProcessTimespanColumn(valueAsString);
}
else if (columnType == typeof(DateTimeOffset))
{
Value = DateTimeOffset.Parse(valueAsString, CultureInfo.CurrentCulture);
ValueAsString = Value.ToString();
}
else if (columnType == typeof(bool))
{
ProcessBooleanCell(valueAsString);
}
// @TODO: Microsoft.SqlServer.Types.SqlHierarchyId
else
{
// Attempt to go straight to the destination type, if we know what it is, otherwise
// leave it as a string
Value = columnType != null
? Convert.ChangeType(valueAsString, columnType, CultureInfo.CurrentCulture)
: valueAsString;
ValueAsString = Value.ToString();
} }
} }
@@ -181,8 +192,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
} }
else else
{ {
// Invalid format throw new InvalidOperationException(SR.EditDataInvalidFormatBinary);
throw new FormatException(SR.EditDataInvalidFormatBinary);
} }
// Generate the hex string as the return value // Generate the hex string as the return value
@@ -205,8 +215,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
Value = false; Value = false;
break; break;
default: default:
throw new ArgumentOutOfRangeException(nameof(valueAsString), throw new InvalidOperationException(SR.EditDataInvalidFormatBoolean);
SR.EditDataInvalidFormatBoolean);
} }
} }
else else
@@ -253,7 +262,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{ {
string columnSizeString = $"({Column.ColumnSize.Value})"; string columnSizeString = $"({Column.ColumnSize.Value})";
string columnTypeString = Column.DataTypeName.ToUpperInvariant() + columnSizeString; string columnTypeString = Column.DataTypeName.ToUpperInvariant() + columnSizeString;
throw new FormatException(SR.EditDataValueTooLarge(valueAsString, columnTypeString)); throw new InvalidOperationException(SR.EditDataValueTooLarge(valueAsString, columnTypeString));
} }
ValueAsString = valueAsString; ValueAsString = valueAsString;

View File

@@ -14,7 +14,7 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
@@ -202,7 +202,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
// Add an out column if we're doing this for a command // Add an out column if we're doing this for a command
if (forCommand) if (forCommand)
{ {
outColumns.Add($"inserted.{SqlScriptFormatter.FormatIdentifier(column.ColumnName)}"); outColumns.Add($"inserted.{ToSqlScript.FormatIdentifier(column.ColumnName)}");
} }
// Skip columns that cannot be updated // Skip columns that cannot be updated
@@ -237,11 +237,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
else else
{ {
// This script isn't for command use, add the value, formatted for insertion // This script isn't for command use, add the value, formatted for insertion
inValues.Add(SqlScriptFormatter.FormatValue(cell.Value, column)); inValues.Add(ToSqlScript.FormatValue(cell.Value, column));
} }
// Add the column to the in columns // Add the column to the in columns
inColumns.Add(SqlScriptFormatter.FormatIdentifier(column.ColumnName)); inColumns.Add(ToSqlScript.FormatIdentifier(column.ColumnName));
} }
// Begin the script (ie, INSERT INTO blah) // Begin the script (ie, INSERT INTO blah)

View File

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{ {
@@ -27,6 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// <summary> /// <summary>
/// Internal parameterless constructor, required for mocking /// Internal parameterless constructor, required for mocking
/// </summary> /// </summary>
// ReSharper disable once UnusedMember.Global
protected internal RowEditBase() { } protected internal RowEditBase() { }
/// <summary> /// <summary>
@@ -204,7 +205,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
else else
{ {
// Add the clause component with the formatted value // Add the clause component with the formatted value
cellDataClause = $"= {SqlScriptFormatter.FormatValue(cellData, col.DbColumn)}"; cellDataClause = $"= {ToSqlScript.FormatValue(cellData, col.DbColumn)}";
} }
} }
} }

View File

@@ -13,7 +13,7 @@ using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts; using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
@@ -81,7 +81,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
List<string> setComponents = new List<string>(); List<string> setComponents = new List<string>();
foreach (var updateElement in cellUpdates) foreach (var updateElement in cellUpdates)
{ {
string formattedColumnName = SqlScriptFormatter.FormatIdentifier(updateElement.Value.Column.ColumnName); string formattedColumnName = ToSqlScript.FormatIdentifier(updateElement.Value.Column.ColumnName);
string paramName = $"@Value{RowId}_{updateElement.Key}"; string paramName = $"@Value{RowId}_{updateElement.Key}";
setComponents.Add($"{formattedColumnName} = {paramName}"); setComponents.Add($"{formattedColumnName} = {paramName}");
SqlParameter parameter = new SqlParameter(paramName, updateElement.Value.Column.SqlDbType) SqlParameter parameter = new SqlParameter(paramName, updateElement.Value.Column.SqlDbType)
@@ -94,7 +94,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
// Build the "OUTPUT" portion of the statement // Build the "OUTPUT" portion of the statement
var outColumns = from c in AssociatedResultSet.Columns var outColumns = from c in AssociatedResultSet.Columns
let formatted = SqlScriptFormatter.FormatIdentifier(c.ColumnName) let formatted = ToSqlScript.FormatIdentifier(c.ColumnName)
select $"inserted.{formatted}"; select $"inserted.{formatted}";
string outColumnsJoined = string.Join(", ", outColumns); string outColumnsJoined = string.Join(", ", outColumns);
@@ -148,8 +148,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
// Build the "SET" portion of the statement // Build the "SET" portion of the statement
var setComponents = cellUpdates.Values.Select(cellUpdate => var setComponents = cellUpdates.Values.Select(cellUpdate =>
{ {
string formattedColumnName = SqlScriptFormatter.FormatIdentifier(cellUpdate.Column.ColumnName); string formattedColumnName = ToSqlScript.FormatIdentifier(cellUpdate.Column.ColumnName);
string formattedValue = SqlScriptFormatter.FormatValue(cellUpdate.Value, cellUpdate.Column); string formattedValue = ToSqlScript.FormatValue(cellUpdate.Value, cellUpdate.Column);
return $"{formattedColumnName} = {formattedValue}"; return $"{formattedColumnName} = {formattedValue}";
}); });
string setClause = string.Join(", ", setComponents); string setClause = string.Join(", ", setComponents);

View File

@@ -885,6 +885,22 @@ namespace Microsoft.SqlTools.ServiceLayer
} }
} }
public static string SqlScriptFormatterLengthTypeMissingSize
{
get
{
return Keys.GetString(Keys.SqlScriptFormatterLengthTypeMissingSize);
}
}
public static string SqlScriptFormatterScalarTypeMissingScale
{
get
{
return Keys.GetString(Keys.SqlScriptFormatterScalarTypeMissingScale);
}
}
public static string TreeNodeError public static string TreeNodeError
{ {
get get
@@ -3667,6 +3683,11 @@ namespace Microsoft.SqlTools.ServiceLayer
return Keys.GetString(Keys.EditDataUnsupportedObjectType, typeName); return Keys.GetString(Keys.EditDataUnsupportedObjectType, typeName);
} }
public static string EditDataInvalidFormat(string colName, string colType)
{
return Keys.GetString(Keys.EditDataInvalidFormat, colName, colType);
}
public static string EditDataCreateScriptMissingValue(string colName) public static string EditDataCreateScriptMissingValue(string colName)
{ {
return Keys.GetString(Keys.EditDataCreateScriptMissingValue, colName); return Keys.GetString(Keys.EditDataCreateScriptMissingValue, colName);
@@ -3922,6 +3943,9 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string EditDataObjectMetadataNotFound = "EditDataObjectMetadataNotFound"; public const string EditDataObjectMetadataNotFound = "EditDataObjectMetadataNotFound";
public const string EditDataInvalidFormat = "EditDataInvalidFormat";
public const string EditDataInvalidFormatBinary = "EditDataInvalidFormatBinary"; public const string EditDataInvalidFormatBinary = "EditDataInvalidFormatBinary";
@@ -4054,6 +4078,12 @@ namespace Microsoft.SqlTools.ServiceLayer
public const string SqlScriptFormatterDecimalMissingPrecision = "SqlScriptFormatterDecimalMissingPrecision"; public const string SqlScriptFormatterDecimalMissingPrecision = "SqlScriptFormatterDecimalMissingPrecision";
public const string SqlScriptFormatterLengthTypeMissingSize = "SqlScriptFormatterLengthTypeMissingSize";
public const string SqlScriptFormatterScalarTypeMissingScale = "SqlScriptFormatterScalarTypeMissingScale";
public const string TreeNodeError = "TreeNodeError"; public const string TreeNodeError = "TreeNodeError";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -447,6 +447,11 @@
<value>Table or view metadata could not be found</value> <value>Table or view metadata could not be found</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="EditDataInvalidFormat" xml:space="preserve">
<value>Invalid format for column '{0}', column is defined as {1}</value>
<comment>.
Parameters: 0 - colName (string), 1 - colType (string) </comment>
</data>
<data name="EditDataInvalidFormatBinary" xml:space="preserve"> <data name="EditDataInvalidFormatBinary" xml:space="preserve">
<value>Invalid format for binary column</value> <value>Invalid format for binary column</value>
<comment></comment> <comment></comment>
@@ -622,7 +627,15 @@
<comment></comment> <comment></comment>
</data> </data>
<data name="SqlScriptFormatterDecimalMissingPrecision" xml:space="preserve"> <data name="SqlScriptFormatterDecimalMissingPrecision" xml:space="preserve">
<value>Decimal column is missing numeric precision or numeric scale</value> <value>Exact numeric column is missing numeric precision or numeric scale</value>
<comment></comment>
</data>
<data name="SqlScriptFormatterLengthTypeMissingSize" xml:space="preserve">
<value>Column with length is missing size</value>
<comment></comment>
</data>
<data name="SqlScriptFormatterScalarTypeMissingScale" xml:space="preserve">
<value>Scalar column missing scale</value>
<comment></comment> <comment></comment>
</data> </data>
<data name="TreeNodeError" xml:space="preserve"> <data name="TreeNodeError" xml:space="preserve">

File diff suppressed because it is too large Load Diff

View File

@@ -208,6 +208,8 @@ EditDataUpdateNotPending = Given row ID does not have pending update
EditDataObjectMetadataNotFound = Table or view metadata could not be found EditDataObjectMetadataNotFound = Table or view metadata could not be found
EditDataInvalidFormat(string colName, string colType) = Invalid format for column '{0}', column is defined as {1}
EditDataInvalidFormatBinary = Invalid format for binary column EditDataInvalidFormatBinary = Invalid format for binary column
EditDataInvalidFormatBoolean = Allowed values for boolean columns are 0, 1, "true", or "false" EditDataInvalidFormatBoolean = Allowed values for boolean columns are 0, 1, "true", or "false"
@@ -303,7 +305,11 @@ TestLocalizationConstant = test
############################################################################ ############################################################################
# Utilities # Utilities
SqlScriptFormatterDecimalMissingPrecision = Decimal column is missing numeric precision or numeric scale SqlScriptFormatterDecimalMissingPrecision = Exact numeric column is missing numeric precision or numeric scale
SqlScriptFormatterLengthTypeMissingSize = Column with length is missing size
SqlScriptFormatterScalarTypeMissingScale = Scalar column missing scale
############################################################################ ############################################################################
# Object Explorer Service # Object Explorer Service

View File

@@ -523,7 +523,7 @@
<note></note> <note></note>
</trans-unit> </trans-unit>
<trans-unit id="SqlScriptFormatterDecimalMissingPrecision"> <trans-unit id="SqlScriptFormatterDecimalMissingPrecision">
<source>Decimal column is missing numeric precision or numeric scale</source> <source>Exact numeric column is missing numeric precision or numeric scale</source>
<target state="new">Decimal column is missing numeric precision or numeric scale</target> <target state="new">Decimal column is missing numeric precision or numeric scale</target>
<note></note> <note></note>
</trans-unit> </trans-unit>
@@ -2312,6 +2312,22 @@
<note>. <note>.
Parameters: 0 - value (string), 1 - columnType (string) </note> Parameters: 0 - value (string), 1 - columnType (string) </note>
</trans-unit> </trans-unit>
<trans-unit id="EditDataInvalidFormat">
<source>Invalid format for column '{0}', column is defined as {1}</source>
<target state="new">Invalid format for column '{0}', column is defined as {1}</target>
<note>.
Parameters: 0 - colName (string), 1 - colType (string) </note>
</trans-unit>
<trans-unit id="SqlScriptFormatterLengthTypeMissingSize">
<source>Column with length is missing size</source>
<target state="new">Column with length is missing size</target>
<note></note>
</trans-unit>
<trans-unit id="SqlScriptFormatterScalarTypeMissingScale">
<source>Scalar column missing scale</source>
<target state="new">Scalar column missing scale</target>
<note></note>
</trans-unit>
<trans-unit id="StoredProcedureScriptParameterComment"> <trans-unit id="StoredProcedureScriptParameterComment">
<source>-- TODO: Set parameter values here.</source> <source>-- TODO: Set parameter values here.</source>
<target state="new">-- TODO: Set parameter values here.</target> <target state="new">-- TODO: Set parameter values here.</target>
@@ -2319,7 +2335,7 @@
</trans-unit> </trans-unit>
<trans-unit id="ScriptingGeneralError"> <trans-unit id="ScriptingGeneralError">
<source>An error occurred while scripting the objects.</source> <source>An error occurred while scripting the objects.</source>
<target state="new">An error occurred while scripting the objects</target> <target state="new">An error occurred while scripting the objects.</target>
<note></note> <note></note>
</trans-unit> </trans-unit>
<trans-unit id="ScriptingExecuteNotSupportedError"> <trans-unit id="ScriptingExecuteNotSupportedError">

View File

@@ -10,7 +10,7 @@ using System.Data.SqlClient;
using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo; using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection; using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
namespace Microsoft.SqlTools.ServiceLayer.Metadata namespace Microsoft.SqlTools.ServiceLayer.Metadata
{ {
@@ -92,13 +92,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
// The default value may be escaped // The default value may be escaped
string defaultValue = smoColumn.DefaultConstraint == null string defaultValue = smoColumn.DefaultConstraint == null
? null ? null
: SqlScriptFormatter.UnwrapLiteral(smoColumn.DefaultConstraint.Text); : FromSqlScript.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
ColumnMetadata column = new ColumnMetadata ColumnMetadata column = new ColumnMetadata
{ {
DefaultValue = defaultValue, DefaultValue = defaultValue,
EscapedName = SqlScriptFormatter.FormatIdentifier(smoColumn.Name), EscapedName = ToSqlScript.FormatIdentifier(smoColumn.Name),
Ordinal = i, Ordinal = i
}; };
editColumns.Add(column); editColumns.Add(column);
} }
@@ -109,7 +109,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Metadata
// Escape the parts of the name // Escape the parts of the name
string[] objectNameParts = {smoResult.Schema, smoResult.Name}; string[] objectNameParts = {smoResult.Schema, smoResult.Name};
string escapedMultipartName = SqlScriptFormatter.FormatMultipartIdentifier(objectNameParts); string escapedMultipartName = ToSqlScript.FormatMultipartIdentifier(objectNameParts);
return new TableMetadata return new TableMetadata
{ {

View File

@@ -18,8 +18,8 @@
<Reference Include="System.Data.SqlClient" /> <Reference Include="System.Data.SqlClient" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Data.SqlClient" Version="4.4.0" /> <PackageReference Include="System.Data.SqlClient" Version="4.4.0" />
<PackageReference Include="Microsoft.SqlServer.Smo" Version="140.2.8" /> <PackageReference Include="Microsoft.SqlServer.Smo" Version="140.2.8" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="**\*.cs" /> <Compile Include="**\*.cs" />

View File

@@ -0,0 +1,149 @@
//
// 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.Text;
using System.Text.RegularExpressions;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters
{
/// <summary>
/// Provides utilities for converting from SQL script syntax into POCOs.
/// </summary>
public static class FromSqlScript
{
// Regex: optionally starts with N, captures string wrapped in single quotes
private static readonly Regex StringRegex = new Regex("^N?'(.*)'$", RegexOptions.Compiled);
/// <summary>
/// Decodes a multipart identifier as used in a SQL script into an array of the multiple
/// parts of the identifier. Implemented as a state machine that iterates over the
/// characters of the multipart identifier.
/// </summary>
/// <param name="multipartIdentifier">Multipart identifier to decode (eg, "[dbo].[test]")</param>
/// <returns>The parts of the multipart identifier in an array (eg, "dbo", "test")</returns>
/// <exception cref="FormatException">
/// Thrown if an invalid state transition is made, indicating that the multipart identifer
/// is not valid.
/// </exception>
public static string[] DecodeMultipartIdentifier(string multipartIdentifier)
{
StringBuilder sb = new StringBuilder();
List<string> namedParts = new List<string>();
bool insideBrackets = false;
bool bracketsClosed = false;
for (int i = 0; i < multipartIdentifier.Length; i++)
{
char iChar = multipartIdentifier[i];
if (insideBrackets)
{
if (iChar == ']')
{
if (HasNextCharacter(multipartIdentifier, ']', i))
{
// This is an escaped ]
sb.Append(iChar);
i++;
}
else
{
// This bracket closes the bracket we were in
insideBrackets = false;
bracketsClosed = true;
}
}
else
{
// This is a standard character
sb.Append(iChar);
}
}
else
{
switch (iChar)
{
case '[':
if (bracketsClosed)
{
throw new FormatException();
}
// We're opening a set of brackets
insideBrackets = true;
bracketsClosed = false;
break;
case '.':
if (sb.Length == 0)
{
throw new FormatException();
}
// We're splitting the identifier into a new part
namedParts.Add(sb.ToString());
sb = new StringBuilder();
bracketsClosed = false;
break;
default:
if (bracketsClosed)
{
throw new FormatException();
}
// This is a standard character
sb.Append(iChar);
break;
}
}
}
if (sb.Length == 0)
{
throw new FormatException();
}
namedParts.Add(sb.ToString());
return namedParts.ToArray();
}
/// <summary>
/// Converts a value from a script into a plain version by unwrapping literal wrappers
/// and unescaping characters.
/// </summary>
/// <param name="literal">The value to unwrap (eg, "(N'foo''bar')")</param>
/// <returns>The unwrapped/unescaped literal (eg, "foo'bar")</returns>
public static string UnwrapLiteral(string literal)
{
// Always remove parens
literal = literal.Trim('(', ')');
// Attempt to unwrap inverted commas around a string
Match match = StringRegex.Match(literal);
if (match.Success)
{
// Like: N'stuff' or 'stuff'
return UnEscapeString(match.Groups[1].Value, '\'');
}
return literal;
}
#region Private Helpers
private static bool HasNextCharacter(string haystack, char needle, int position)
{
return position + 1 < haystack.Length
&& haystack[position + 1] == needle;
}
private static string UnEscapeString(string value, char escapeCharacter)
{
Validate.IsNotNull(nameof(value), value);
// Replace 2x of the escape character with 1x of the escape character
return value.Replace(new string(escapeCharacter, 2), escapeCharacter.ToString());
}
#endregion
}
}

View File

@@ -9,17 +9,16 @@ using System.Data.Common;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.Utility; using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Utility namespace Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters
{ {
/// <summary> /// <summary>
/// Provides utility for converting arbitrary objects into strings that are ready to be /// Provides utility for converting arbitrary objects into strings that are ready to be
/// inserted into SQL strings /// inserted into SQL strings
/// </summary> /// </summary>
public class SqlScriptFormatter public static class ToSqlScript
{ {
#region Constants #region Constants
@@ -27,16 +26,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
private static readonly Dictionary<string, Func<object, DbColumn, string>> FormatFunctions = private static readonly Dictionary<string, Func<object, DbColumn, string>> FormatFunctions =
new Dictionary<string, Func<object, DbColumn, string>> new Dictionary<string, Func<object, DbColumn, string>>
{ // CLR Type -------- { // CLR Type --------
{"bigint", (val, col) => SimpleFormatter(val)}, // long {"bigint", (val, col) => SimpleFormatter(val)}, // long
{"bit", (val, col) => FormatBool(val)}, // bool {"bit", (val, col) => FormatBool(val)}, // bool
{"int", (val, col) => SimpleFormatter(val)}, // int {"int", (val, col) => SimpleFormatter(val)}, // int
{"smallint", (val, col) => SimpleFormatter(val)}, // short {"smallint", (val, col) => SimpleFormatter(val)}, // short
{"tinyint", (val, col) => SimpleFormatter(val)}, // byte {"tinyint", (val, col) => SimpleFormatter(val)}, // byte
{"money", (val, col) => FormatMoney(val, "MONEY")}, // Decimal {"money", FormatDecimalLike}, // Decimal
{"smallmoney", (val, col) => FormatMoney(val, "SMALLMONEY")}, // Decimal {"smallmoney", FormatDecimalLike}, // Decimal
{"decimal", (val, col) => FormatPreciseNumeric(val, col, "DECIMAL")}, // Decimal {"decimal", FormatDecimalLike}, // Decimal
{"numeric", (val, col) => FormatPreciseNumeric(val, col, "NUMERIC")}, // Decimal {"numeric", FormatDecimalLike}, // Decimal
{"real", (val, col) => FormatFloat(val)}, // float {"real", (val, col) => FormatFloat(val)}, // float
{"float", (val, col) => FormatDouble(val)}, // double {"float", (val, col) => FormatDouble(val)}, // double
{"smalldatetime", (val, col) => FormatDateTime(val, "yyyy-MM-dd HH:mm:ss")}, // DateTime {"smalldatetime", (val, col) => FormatDateTime(val, "yyyy-MM-dd HH:mm:ss")}, // DateTime
@@ -65,21 +64,105 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
// sysname - it doesn't appear possible to insert a sysname column // sysname - it doesn't appear possible to insert a sysname column
}; };
private static readonly Type[] NumericTypes =
{
typeof(byte),
typeof(short),
typeof(int),
typeof(long),
typeof(decimal),
typeof(float),
typeof(double)
};
private static Regex StringRegex = new Regex("^N?'(.*)'$", RegexOptions.Compiled);
#endregion #endregion
#region Public Methods
/// <summary>
/// Extracts a DbColumn's datatype and turns it into script ready
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
/// <seealso cref="Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel.SmoColumnCustomNodeHelper.GetTypeSpecifierLabel"/>
/// <exception cref="InvalidOperationException"></exception>
public static string FormatColumnType(DbColumn column)
{
string typeName = column.DataTypeName.ToUpperInvariant();
// TODO: This doesn't support UDTs at all.
// TODO: It's unclear if this will work on a case-sensitive db collation
// If the type supports length parameters, the add those
switch (column.DataTypeName.ToLowerInvariant())
{
// Types with length
case "char":
case "nchar":
case "varchar":
case "nvarchar":
case "binary":
case "varbinary":
if (!column.ColumnSize.HasValue)
{
throw new InvalidOperationException(SR.SqlScriptFormatterLengthTypeMissingSize);
}
string length = column.ColumnSize.Value == int.MaxValue
? "MAX"
: column.ColumnSize.Value.ToString();
typeName += $"({length})";
break;
// Types with precision and scale
case "numeric":
case "decimal":
if (!column.NumericPrecision.HasValue || !column.NumericScale.HasValue)
{
throw new InvalidOperationException(SR.SqlScriptFormatterDecimalMissingPrecision);
}
typeName += $"({column.NumericPrecision}, {column.NumericScale})";
break;
// Types with scale only
case "datetime2":
case "datetimeoffset":
case "time":
if (!column.NumericScale.HasValue)
{
throw new InvalidOperationException(SR.SqlScriptFormatterScalarTypeMissingScale);
}
typeName += $"({column.NumericScale})";
break;
}
return typeName;
}
/// <summary>
/// Escapes an identifier such as a table name or column name by wrapping it in square brackets
/// </summary>
/// <param name="identifier">The identifier to format</param>
/// <returns>Identifier formatted for use in a SQL script</returns>
public static string FormatIdentifier(string identifier)
{
return $"[{EscapeString(identifier, ']')}]";
}
/// <summary>
/// Escapes a multi-part identifier such as a table name or column name with multiple
/// parts split by '.'
/// </summary>
/// <param name="identifier">The identifier to escape (eg, "dbo.test")</param>
/// <returns>The escaped identifier (eg, "[dbo].[test]")</returns>
public static string FormatMultipartIdentifier(string identifier)
{
// If the object is a multi-part identifier (eg, dbo.tablename) split it, and escape as necessary
return FormatMultipartIdentifier(identifier.Split('.'));
}
/// <summary>
/// Escapes a multipart identifier such as a table name, given an array of the parts of the
/// multipart identifier.
/// </summary>
/// <param name="identifiers">The parts of the identifier to escape (eg, "dbo", "test")</param>
/// <returns>An escaped version of the multipart identifier (eg, "[dbo].[test]")</returns>
public static string FormatMultipartIdentifier(string[] identifiers)
{
IEnumerable<string> escapedParts = identifiers.Select(FormatIdentifier);
return string.Join(".", escapedParts);
}
/// <summary> /// <summary>
/// Converts an object into a string for SQL script /// Converts an object into a string for SQL script
/// </summary> /// </summary>
@@ -121,228 +204,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
return FormatValue(value.RawObject, column); return FormatValue(value.RawObject, column);
} }
/// <summary> #endregion
/// Escapes an identifier such as a table name or column name by wrapping it in square brackets
/// </summary>
/// <param name="identifier">The identifier to format</param>
/// <returns>Identifier formatted for use in a SQL script</returns>
public static string FormatIdentifier(string identifier)
{
return $"[{EscapeString(identifier, ']')}]";
}
/// <summary>
/// Escapes a multi-part identifier such as a table name or column name with multiple
/// parts split by '.'
/// </summary>
/// <param name="identifier">The identifier to escape</param>
/// <returns>The escaped identifier</returns>
public static string FormatMultipartIdentifier(string identifier)
{
// If the object is a multi-part identifier (eg, dbo.tablename) split it, and escape as necessary
return FormatMultipartIdentifier(identifier.Split('.'));
}
/// <summary>
/// Escapes a multipart identifier such as a table name, given an array of the parts of the
/// multipart identifier.
/// </summary>
/// <param name="identifiers">The parts of the identifier to escape</param>
/// <returns>An escaped version of the multipart identifier</returns>
public static string FormatMultipartIdentifier(string[] identifiers)
{
IEnumerable<string> escapedParts = identifiers.Select(FormatIdentifier);
return string.Join(".", escapedParts);
}
/// <summary>
/// Converts a value from a script into a plain version by unwrapping literal wrappers
/// and unescaping characters.
/// </summary>
/// <param name="literal">The value to unwrap</param>
/// <returns>The unwrapped/unescaped literal</returns>
public static string UnwrapLiteral(string literal)
{
// Always remove parens
literal = literal.Trim('(', ')');
// Attempt to unwrap inverted commas around a string
Match match = StringRegex.Match(literal);
if (match.Success)
{
// Like: N'stuff' or 'stuff'
return UnEscapeString(match.Groups[1].Value, '\'');
}
return literal;
}
public static string[] DecodeMultipartIdenfitier(string multipartIdentifier)
{
StringBuilder sb = new StringBuilder();
List<string> namedParts = new List<string>();
bool insideBrackets = false;
bool bracketsClosed = false;
for (int i = 0; i < multipartIdentifier.Length; i++)
{
char iChar = multipartIdentifier[i];
if (insideBrackets)
{
if (iChar == ']')
{
if (HasNextCharacter(multipartIdentifier, ']', i))
{
// This is an escaped ]
sb.Append(iChar);
i++;
}
else
{
// This bracket closes the bracket we were in
insideBrackets = false;
bracketsClosed = true;
}
}
else
{
// This is a standard character
sb.Append(iChar);
}
}
else
{
switch (iChar)
{
case '[':
if (bracketsClosed)
{
throw new FormatException();
}
// We're opening a set of brackets
insideBrackets = true;
bracketsClosed = false;
break;
case '.':
if (sb.Length == 0)
{
throw new FormatException();
}
// We're splitting the identifier into a new part
namedParts.Add(sb.ToString());
sb = new StringBuilder();
bracketsClosed = false;
break;
default:
if (bracketsClosed)
{
throw new FormatException();
}
// This is a standard character
sb.Append(iChar);
break;
}
}
}
if (sb.Length == 0)
{
throw new FormatException();
}
namedParts.Add(sb.ToString());
return namedParts.ToArray();
}
#region Private Helpers #region Private Helpers
private static string SimpleFormatter(object value)
{
return value.ToString();
}
private static string SimpleStringFormatter(object value)
{
return EscapeQuotedSqlString(value.ToString());
}
private static string FormatMoney(object value, string type)
{
// we have to manually format the string by ToStringing the value first, and then converting
// the potential (European formatted) comma to a period.
string numericString = ((decimal)value).ToString(CultureInfo.InvariantCulture);
return $"CAST({numericString} AS {type})";
}
private static string FormatFloat(object value)
{
// The "R" formatting means "Round Trip", which preserves fidelity
return ((float)value).ToString("R");
}
private static string FormatDouble(object value)
{
// The "R" formatting means "Round Trip", which preserves fidelity
return ((double)value).ToString("R");
}
private static string FormatBool(object value)
{
// Attempt to cast to bool
bool boolValue = (bool)value;
return boolValue ? "1" : "0";
}
private static string FormatPreciseNumeric(object value, DbColumn column, string type)
{
// Make sure we have numeric precision and numeric scale
if (!column.NumericPrecision.HasValue || !column.NumericScale.HasValue)
{
throw new InvalidOperationException(SR.SqlScriptFormatterDecimalMissingPrecision);
}
// Convert the value to a decimal, then convert that to a string
string numericString = ((decimal)value).ToString(CultureInfo.InvariantCulture);
return string.Format(CultureInfo.InvariantCulture, "CAST({0} AS {1}({2}, {3}))",
numericString, type, column.NumericPrecision.Value, column.NumericScale.Value);
}
private static string FormatTimeSpan(object value)
{
// "c" provides "HH:mm:ss.FFFFFFF", and time column accepts up to 7 precision
string timeSpanString = ((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(timeSpanString);
}
private static string FormatDateTime(object value, string format)
{
string dateTimeString = ((DateTime)value).ToString(format, CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(dateTimeString);
}
private static string FormatDateTimeOffset(object value)
{
string dateTimeString = ((DateTimeOffset)value).ToString(CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(dateTimeString);
}
private static string FormatBinary(object value)
{
byte[] bytes = value as byte[];
if (bytes == null)
{
// Bypass processing if we can't turn this into a byte[]
return "NULL";
}
return "0x" + BitConverter.ToString(bytes).Replace("-", string.Empty);
}
private static bool HasNextCharacter(string haystack, char needle, int position)
{
return position + 1 < haystack.Length
&& haystack[position + 1] == needle;
}
/// <summary> /// <summary>
/// Returns a valid SQL string packaged in single quotes with single quotes inside escaped /// Returns a valid SQL string packaged in single quotes with single quotes inside escaped
/// </summary> /// </summary>
@@ -376,12 +241,71 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
return sb.ToString(); return sb.ToString();
} }
private static string UnEscapeString(string value, char escapeCharacter) private static string FormatBinary(object value)
{ {
Validate.IsNotNull(nameof(value), value); byte[] bytes = value as byte[];
if (bytes == null)
{
// Bypass processing if we can't turn this into a byte[]
return "NULL";
}
// Replace 2x of the escape character with 1x of the escape character return "0x" + BitConverter.ToString(bytes).Replace("-", string.Empty);
return value.Replace(new string(escapeCharacter, 2), escapeCharacter.ToString()); }
private static string FormatBool(object value)
{
// Attempt to cast to bool
bool boolValue = (bool)value;
return boolValue ? "1" : "0";
}
private static string FormatDateTime(object value, string format)
{
string dateTimeString = ((DateTime)value).ToString(format, CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(dateTimeString);
}
private static string FormatDateTimeOffset(object value)
{
string dateTimeString = ((DateTimeOffset)value).ToString(CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(dateTimeString);
}
private static string FormatDouble(object value)
{
// The "R" formatting means "Round Trip", which preserves fidelity
return ((double)value).ToString("R");
}
private static string FormatFloat(object value)
{
// The "R" formatting means "Round Trip", which preserves fidelity
return ((float)value).ToString("R");
}
private static string FormatDecimalLike(object value, DbColumn column)
{
string numericString = ((decimal)value).ToString(CultureInfo.InvariantCulture);
string typeString = FormatColumnType(column);
return $"CAST({numericString} AS {typeString})";
}
private static string FormatTimeSpan(object value)
{
// "c" provides "HH:mm:ss.FFFFFFF", and time column accepts up to 7 precision
string timeSpanString = ((TimeSpan)value).ToString("c", CultureInfo.InvariantCulture);
return EscapeQuotedSqlString(timeSpanString);
}
private static string SimpleFormatter(object value)
{
return value.ToString();
}
private static string SimpleStringFormatter(object value)
{
return EscapeQuotedSqlString(value.ToString());
} }
#endregion #endregion

View File

@@ -77,7 +77,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
// If: I attempt to create a CellUpdate to set it to a large string // If: I attempt to create a CellUpdate to set it to a large string
// Then: I should get an exception thrown // Then: I should get an exception thrown
DbColumnWrapper col = GetWrapper<string>("nvarchar", false, 6); DbColumnWrapper col = GetWrapper<string>("nvarchar", false, 6);
Assert.Throws<FormatException>(() => new CellUpdate(col, value)); Assert.Throws<InvalidOperationException>(() => new CellUpdate(col, value));
} }
[Theory] [Theory]
@@ -140,7 +140,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
// If: I attempt to create a CellUpdate for a binary column // If: I attempt to create a CellUpdate for a binary column
// Then: It should throw an exception // Then: It should throw an exception
DbColumnWrapper col = GetWrapper<byte[]>("binary"); DbColumnWrapper col = GetWrapper<byte[]>("binary");
Assert.Throws<FormatException>(() => new CellUpdate(col, "this is totally invalid")); Assert.Throws<InvalidOperationException>(() => new CellUpdate(col, "this is totally invalid"));
} }
[Theory] [Theory]
@@ -175,7 +175,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
// If: I create a CellUpdate for a bool column and provide an invalid numeric value // If: I create a CellUpdate for a bool column and provide an invalid numeric value
// Then: It should throw an exception // Then: It should throw an exception
DbColumnWrapper col = GetWrapper<bool>("bit"); DbColumnWrapper col = GetWrapper<bool>("bit");
Assert.Throws<ArgumentOutOfRangeException>(() => new CellUpdate(col, "12345")); Assert.Throws<InvalidOperationException>(() => new CellUpdate(col, "12345"));
} }
[Theory] [Theory]

View File

@@ -0,0 +1,79 @@
//
// 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 Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.UtilityTests
{
public class FromSqlScriptTests
{
#region DecodeMultipartIdentifier Tests
public static IEnumerable<object> DecodeMultipartIdentifierTestData
{
get
{
yield return new object[] {"identifier", new[] {"identifier"}};
yield return new object[] {"simple.split", new[] {"simple", "split"}};
yield return new object[] {"multi.simple.split", new[] {"multi", "simple", "split"}};
yield return new object[] {"[escaped]", new[] {"escaped"}};
yield return new object[] {"[escaped].[split]", new[] {"escaped", "split"}};
yield return new object[] {"[multi].[escaped].[split]", new[] {"multi", "escaped", "split"}};
yield return new object[] {"[escaped]]characters]", new[] {"escaped]characters"}};
yield return new object[] {"[multi]]escaped]]chars]", new[] {"multi]escaped]chars"}};
yield return new object[] {"[multi]]]]chars]", new[] {"multi]]chars"}};
yield return new object[] {"unescaped]chars", new[] {"unescaped]chars"}};
yield return new object[] {"multi]unescaped]chars", new[] {"multi]unescaped]chars"}};
yield return new object[] {"multi]]chars", new[] {"multi]]chars"}};
yield return new object[] {"[escaped.dot]", new[] {"escaped.dot"}};
yield return new object[] {"mixed.[escaped]", new[] {"mixed", "escaped"}};
yield return new object[] {"[escaped].mixed", new[] {"escaped", "mixed"}};
yield return new object[] {"dbo.[[].weird", new[] {"dbo", "[", "weird"}};
}
}
[Theory]
[MemberData(nameof(DecodeMultipartIdentifierTestData))]
public void DecodeMultipartIdentifierTest(string input, string[] output)
{
// If: I decode the input
string[] decoded = FromSqlScript.DecodeMultipartIdentifier(input);
// Then: The output should match what was expected
Assert.Equal(output, decoded);
}
[Theory]
[InlineData("[bracket]closed")]
[InlineData("[bracket][closed")]
[InlineData(".stuff")]
[InlineData(".")]
public void DecodeMultipartIdentifierFailTest(string input)
{
// If: I decode an invalid input
// Then: It should throw an exception
Assert.Throws<FormatException>(() => FromSqlScript.DecodeMultipartIdentifier(input));
}
#endregion
[Theory]
[InlineData("(0)", "0")]
[InlineData("((0))", "0")]
[InlineData("('')", "")]
[InlineData("('stuff')", "stuff")]
[InlineData("(N'')", "")]
[InlineData("(N'stuff')", "stuff")]
[InlineData("('''stuff')", "'stuff")]
[InlineData("(N'stu''''ff')", "stu''ff")]
public void UnescapeTest(string input, string output)
{
Assert.Equal(output, FromSqlScript.UnwrapLiteral(input));
}
}
}

View File

@@ -1,83 +1,16 @@
// using System;
// 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.Collections.Generic;
using System.Data.Common; using System.Data.Common;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Microsoft.SqlTools.ServiceLayer.Utility.SqlScriptFormatters;
using Xunit; using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility namespace Microsoft.SqlTools.ServiceLayer.UnitTests.UtilityTests
{ {
public class SqlScriptFormatterTests public class ToSqlScriptTests
{ {
#region Format Identifier Tests
[Fact]
public void FormatIdentifierNull()
{
// If: I attempt to format null as an identifier
// Then: I should get an exception thrown
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatIdentifier(null));
}
[Theory]
[InlineData("test", "[test]")] // No escape characters
[InlineData("]test", "[]]test]")] // Escape character at beginning
[InlineData("te]st", "[te]]st]")] // Escape character in middle
[InlineData("test]", "[test]]]")] // Escape character at end
[InlineData("t]]est", "[t]]]]est]")] // Multiple escape characters
public void FormatIdentifierTest(string value, string expectedOutput)
{
// If: I attempt to format a value as an identifier
string output = SqlScriptFormatter.FormatIdentifier(value);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
[Theory]
[InlineData("test", "[test]")] // No splits, no escape characters
[InlineData("test.test", "[test].[test]")] // One split, no escape characters
[InlineData("test.te]st", "[test].[te]]st]")] // One split, one escape character
[InlineData("test.test.test", "[test].[test].[test]")] // Two splits, no escape characters
public void FormatMultipartIdentifierTest(string value, string expectedOutput)
{
// If: I attempt to format a value as a multipart identifier
string output = SqlScriptFormatter.FormatMultipartIdentifier(value);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
[Theory]
[MemberData(nameof(GetMultipartIdentifierArrays))]
public void FormatMultipartIdentifierArrayTest(string expectedOutput, string[] splits)
{
// If: I attempt to format a value as a multipart identifier
string output = SqlScriptFormatter.FormatMultipartIdentifier(splits);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
public static IEnumerable<object[]> GetMultipartIdentifierArrays
{
get
{
yield return new object[] {"[test]", new[] {"test"}}; // No splits, no escape characters
yield return new object[] {"[test].[test]", new[] {"test", "test"}}; // One split, no escape characters
yield return new object[] {"[test].[te]]st]", new[] {"test", "te]st"}}; // One split, one escape character
yield return new object[] {"[test].[test].[test]", new[] {"test", "test", "test"}}; // Two splits, no escape characters
}
}
#endregion
#region FormatValue Tests #region FormatValue Tests
[Fact] [Fact]
@@ -85,7 +18,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
{ {
// If: I attempt to format a null db cell // If: I attempt to format a null db cell
// Then: It should throw // Then: It should throw
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatValue(null, new FormatterTestDbColumn(null))); DbColumn column = new FormatterTestDbColumn(null);
Assert.Throws<ArgumentNullException>(() => ToSqlScript.FormatValue(null, column));
} }
[Fact] [Fact]
@@ -93,7 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
{ {
// If: I attempt to format a null db column // If: I attempt to format a null db column
// Then: It should throw // Then: It should throw
Assert.Throws<ArgumentNullException>(() => SqlScriptFormatter.FormatValue(new DbCellValue(), null)); Assert.Throws<ArgumentNullException>(() => ToSqlScript.FormatValue(new DbCellValue(), null));
} }
public void UnsupportedColumnTest() public void UnsupportedColumnTest()
@@ -101,7 +35,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
// If: I attempt to format an unsupported datatype // If: I attempt to format an unsupported datatype
// Then: It should throw // Then: It should throw
DbColumn column = new FormatterTestDbColumn("unsupported"); DbColumn column = new FormatterTestDbColumn("unsupported");
Assert.Throws<ArgumentOutOfRangeException>(() => SqlScriptFormatter.FormatValue(new DbCellValue(), column)); Assert.Throws<ArgumentOutOfRangeException>(() => ToSqlScript.FormatValue(new DbCellValue(), column));
} }
[Fact] [Fact]
@@ -109,8 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
{ {
// If: I attempt to format a db cell that contains null // If: I attempt to format a db cell that contains null
// Then: I should get the null string back // Then: I should get the null string back
string formattedString = SqlScriptFormatter.FormatValue(new DbCellValue(), new FormatterTestDbColumn(null)); DbColumn column = new FormatterTestDbColumn(null);
Assert.Equal(SqlScriptFormatter.NullString, formattedString); string formattedString = ToSqlScript.FormatValue(new DbCellValue(), new FormatterTestDbColumn(null));
Assert.Equal(ToSqlScript.NullString, formattedString);
} }
@@ -126,7 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = (long)123 }; DbCellValue cell = new DbCellValue { RawObject = (long)123 };
// If: I attempt to format an integer type column // If: I attempt to format an integer type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a long // Then: The output string should be able to be converted back into a long
Assert.Equal(cell.RawObject, long.Parse(output)); Assert.Equal(cell.RawObject, long.Parse(output));
@@ -144,7 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = 123.45m }; DbCellValue cell = new DbCellValue { RawObject = 123.45m };
// If: I attempt to format a decimal type column // If: I attempt to format a decimal type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: It should match a something like CAST(123.45 AS MONEY) // Then: It should match a something like CAST(123.45 AS MONEY)
Regex castRegex = new Regex($@"CAST\([\d\.]+ AS {regex}", RegexOptions.IgnoreCase); Regex castRegex = new Regex($@"CAST\([\d\.]+ AS {regex}", RegexOptions.IgnoreCase);
@@ -159,7 +94,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = 3.14159d }; DbCellValue cell = new DbCellValue { RawObject = 3.14159d };
// If: I attempt to format a approx numeric type column // If: I attempt to format a approx numeric type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a double // Then: The output string should be able to be converted back into a double
Assert.Equal(cell.RawObject, double.Parse(output)); Assert.Equal(cell.RawObject, double.Parse(output));
@@ -173,7 +108,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = (float)3.14159 }; DbCellValue cell = new DbCellValue { RawObject = (float)3.14159 };
// If: I attempt to format a approx numeric type column // If: I attempt to format a approx numeric type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a double // Then: The output string should be able to be converted back into a double
Assert.Equal(cell.RawObject, float.Parse(output)); Assert.Equal(cell.RawObject, float.Parse(output));
@@ -191,7 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = DateTime.Now }; DbCellValue cell = new DbCellValue { RawObject = DateTime.Now };
// If: I attempt to format a datetime type column // If: I attempt to format a datetime type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a datetime // Then: The output string should be able to be converted back into a datetime
Regex dateTimeRegex = new Regex("N'(.*)'"); Regex dateTimeRegex = new Regex("N'(.*)'");
@@ -207,7 +142,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = DateTimeOffset.Now }; DbCellValue cell = new DbCellValue { RawObject = DateTimeOffset.Now };
// If: I attempt to format a datetime offset type column // If: I attempt to format a datetime offset type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a datetime offset // Then: The output string should be able to be converted back into a datetime offset
Regex dateTimeRegex = new Regex("N'(.*)'"); Regex dateTimeRegex = new Regex("N'(.*)'");
@@ -223,7 +158,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = TimeSpan.FromHours(12) }; DbCellValue cell = new DbCellValue { RawObject = TimeSpan.FromHours(12) };
// If: I attempt to format a time type column // If: I attempt to format a time type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be able to be converted back into a timespan // Then: The output string should be able to be converted back into a timespan
Regex dateTimeRegex = new Regex("N'(.*)'"); Regex dateTimeRegex = new Regex("N'(.*)'");
@@ -244,7 +179,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = input }; DbCellValue cell = new DbCellValue { RawObject = input };
// If: I attempt to format a string type column // If: I attempt to format a string type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should be quoted and escaped properly // Then: The output string should be quoted and escaped properly
Assert.Equal(expectedOutput, output); Assert.Equal(expectedOutput, output);
@@ -264,7 +199,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = "test string" }; DbCellValue cell = new DbCellValue { RawObject = "test string" };
// If: I attempt to format a string type column // If: I attempt to format a string type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should match the output string // Then: The output string should match the output string
Assert.Equal("N'test string'", output); Assert.Equal("N'test string'", output);
@@ -284,7 +219,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
}; };
// If: I attempt to format a string type column // If: I attempt to format a string type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should match the output string // Then: The output string should match the output string
Regex regex = new Regex("0x[0-9A-F]+", RegexOptions.IgnoreCase); Regex regex = new Regex("0x[0-9A-F]+", RegexOptions.IgnoreCase);
@@ -299,7 +234,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
DbCellValue cell = new DbCellValue { RawObject = Guid.NewGuid() }; DbCellValue cell = new DbCellValue { RawObject = Guid.NewGuid() };
// If: I attempt to format a string type column // If: I attempt to format a string type column
string output = SqlScriptFormatter.FormatValue(cell, column); string output = ToSqlScript.FormatValue(cell, column);
// Then: The output string should match the output string // Then: The output string should match the output string
Regex regex = new Regex(@"N'[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}'", RegexOptions.IgnoreCase); Regex regex = new Regex(@"N'[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}'", RegexOptions.IgnoreCase);
@@ -308,77 +243,144 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility
#endregion #endregion
#region DecodeMultipartIdentifier Tests #region Format Identifier Tests
[Theory] [Fact]
[MemberData(nameof(DecodeMultipartIdentifierTestData))] public void FormatIdentifierNull()
public void DecodeMultipartIdentifierTest(string input, string[] output)
{ {
// If: I decode the input // If: I attempt to format null as an identifier
string[] decoded = SqlScriptFormatter.DecodeMultipartIdenfitier(input); // Then: I should get an exception thrown
Assert.Throws<ArgumentNullException>(() => ToSqlScript.FormatIdentifier(null));
// Then: The output should match what was expected
Assert.Equal(output, decoded);
} }
public static IEnumerable<object> DecodeMultipartIdentifierTestData [Theory]
[InlineData("test", "[test]")] // No escape characters
[InlineData("]test", "[]]test]")] // Escape character at beginning
[InlineData("te]st", "[te]]st]")] // Escape character in middle
[InlineData("test]", "[test]]]")] // Escape character at end
[InlineData("t]]est", "[t]]]]est]")] // Multiple escape characters
public void FormatIdentifierTest(string value, string expectedOutput)
{
// If: I attempt to format a value as an identifier
string output = ToSqlScript.FormatIdentifier(value);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
[Theory]
[InlineData("test", "[test]")] // No splits, no escape characters
[InlineData("test.test", "[test].[test]")] // One split, no escape characters
[InlineData("test.te]st", "[test].[te]]st]")] // One split, one escape character
[InlineData("test.test.test", "[test].[test].[test]")] // Two splits, no escape characters
public void FormatMultipartIdentifierTest(string value, string expectedOutput)
{
// If: I attempt to format a value as a multipart identifier
string output = ToSqlScript.FormatMultipartIdentifier(value);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
public static IEnumerable<object[]> GetMultipartIdentifierArrays
{ {
get get
{ {
yield return new object[] {"identifier", new[] {"identifier"}}; yield return new object[] {"[test]", new[] {"test"}}; // No splits, no escape characters
yield return new object[] {"simple.split", new[] {"simple", "split"}}; yield return new object[] {"[test].[test]", new[] {"test", "test"}}; // One split, no escape characters
yield return new object[] {"multi.simple.split", new[] {"multi", "simple", "split"}}; yield return new object[] {"[test].[te]]st]", new[] {"test", "te]st"}}; // One split, one escape character
yield return new object[] {"[escaped]", new[] {"escaped"}}; yield return new object[] {"[test].[test].[test]", new[] {"test", "test", "test"}}; // Two splits, no escape characters
yield return new object[] {"[escaped].[split]", new[] {"escaped", "split"}};
yield return new object[] {"[multi].[escaped].[split]", new[] {"multi", "escaped", "split"}};
yield return new object[] {"[escaped]]characters]", new[] {"escaped]characters"}};
yield return new object[] {"[multi]]escaped]]chars]", new[] {"multi]escaped]chars"}};
yield return new object[] {"[multi]]]]chars]", new[] {"multi]]chars"}};
yield return new object[] {"unescaped]chars", new[] {"unescaped]chars"}};
yield return new object[] {"multi]unescaped]chars", new[] {"multi]unescaped]chars"}};
yield return new object[] {"multi]]chars", new[] {"multi]]chars"}};
yield return new object[] {"[escaped.dot]", new[] {"escaped.dot"}};
yield return new object[] {"mixed.[escaped]", new[] {"mixed", "escaped"}};
yield return new object[] {"[escaped].mixed", new[] {"escaped", "mixed"}};
yield return new object[] {"dbo.[[].weird", new[] {"dbo", "[", "weird"}};
} }
} }
[Theory] [Theory]
[InlineData("[bracket]closed")] [MemberData(nameof(GetMultipartIdentifierArrays))]
[InlineData("[bracket][closed")] public void FormatMultipartIdentifierArrayTest(string expectedOutput, string[] splits)
[InlineData(".stuff")]
[InlineData(".")]
public void DecodeMultipartIdentifierFailTest(string input)
{ {
// If: I decode an invalid input // If: I attempt to format a value as a multipart identifier
// Then: It should throw an exception string output = ToSqlScript.FormatMultipartIdentifier(splits);
Assert.Throws<FormatException>(() => SqlScriptFormatter.DecodeMultipartIdenfitier(input));
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
} }
#endregion #endregion
[Theory] #region FormatColumnType Tests
[InlineData("(0)", "0")]
[InlineData("((0))", "0")] public static IEnumerable<object[]> FormatColumnTypeData
[InlineData("('')", "")]
[InlineData("('stuff')", "stuff")]
[InlineData("(N'')", "")]
[InlineData("(N'stuff')", "stuff")]
[InlineData("('''stuff')", "'stuff")]
[InlineData("(N'stu''''ff')", "stu''ff")]
public void UnescapeTest(string input, string output)
{ {
Assert.Equal(output, SqlScriptFormatter.UnwrapLiteral(input)); get
{
yield return new object[] {new FormatterTestDbColumn("biGint"), "BIGINT"};
yield return new object[] {new FormatterTestDbColumn("biT"), "BIT"};
yield return new object[] {new FormatterTestDbColumn("deCimal", precision: 18, scale: 0), "DECIMAL(18, 0)"};
yield return new object[] {new FormatterTestDbColumn("deCimal", precision: 22, scale: 2), "DECIMAL(22, 2)"};
yield return new object[] {new FormatterTestDbColumn("inT"), "INT"};
yield return new object[] {new FormatterTestDbColumn("moNey"), "MONEY"};
yield return new object[] {new FormatterTestDbColumn("nuMeric", precision: 18, scale: 0), "NUMERIC(18, 0)"};
yield return new object[] {new FormatterTestDbColumn("nuMeric", precision: 22, scale: 2), "NUMERIC(22, 2)"};
yield return new object[] {new FormatterTestDbColumn("smAllint"), "SMALLINT"};
yield return new object[] {new FormatterTestDbColumn("smAllmoney"), "SMALLMONEY"};
yield return new object[] {new FormatterTestDbColumn("tiNyint"), "TINYINT"};
yield return new object[] {new FormatterTestDbColumn("biNary", size: 255), "BINARY(255)"};
yield return new object[] {new FormatterTestDbColumn("biNary", size: 10), "BINARY(10)"};
yield return new object[] {new FormatterTestDbColumn("vaRbinary", size: 255), "VARBINARY(255)"};
yield return new object[] {new FormatterTestDbColumn("vaRbinary", size: 10), "VARBINARY(10)"};
yield return new object[] {new FormatterTestDbColumn("vaRbinary", size: int.MaxValue), "VARBINARY(MAX)"};
yield return new object[] {new FormatterTestDbColumn("imAge"), "IMAGE"};
yield return new object[] {new FormatterTestDbColumn("smAlldatetime"), "SMALLDATETIME"};
yield return new object[] {new FormatterTestDbColumn("daTetime"), "DATETIME"};
yield return new object[] {new FormatterTestDbColumn("daTetime2", scale: 7), "DATETIME2(7)"};
yield return new object[] {new FormatterTestDbColumn("daTetime2", scale: 0), "DATETIME2(0)"};
yield return new object[] {new FormatterTestDbColumn("daTetimeoffset", scale: 7), "DATETIMEOFFSET(7)"};
yield return new object[] {new FormatterTestDbColumn("daTetimeoffset", scale: 0), "DATETIMEOFFSET(0)"};
yield return new object[] {new FormatterTestDbColumn("tiMe", scale: 7), "TIME(7)"};
yield return new object[] {new FormatterTestDbColumn("flOat"), "FLOAT"};
yield return new object[] {new FormatterTestDbColumn("reAl"), "REAL"};
yield return new object[] {new FormatterTestDbColumn("chAr", size: 1), "CHAR(1)"};
yield return new object[] {new FormatterTestDbColumn("chAr", size: 255), "CHAR(255)"};
yield return new object[] {new FormatterTestDbColumn("ncHar", size: 1), "NCHAR(1)"};
yield return new object[] {new FormatterTestDbColumn("ncHar", size: 255), "NCHAR(255)"};
yield return new object[] {new FormatterTestDbColumn("vaRchar", size: 1), "VARCHAR(1)"};
yield return new object[] {new FormatterTestDbColumn("vaRchar", size: 255), "VARCHAR(255)"};
yield return new object[] {new FormatterTestDbColumn("vaRchar", size: int.MaxValue), "VARCHAR(MAX)"};
yield return new object[] {new FormatterTestDbColumn("nvArchar", size: 1), "NVARCHAR(1)"};
yield return new object[] {new FormatterTestDbColumn("nvArchar", size: 255), "NVARCHAR(255)"};
yield return new object[] {new FormatterTestDbColumn("nvArchar", size: int.MaxValue), "NVARCHAR(MAX)"};
yield return new object[] {new FormatterTestDbColumn("teXt"), "TEXT"};
yield return new object[] {new FormatterTestDbColumn("nteXt"), "NTEXT"};
yield return new object[] {new FormatterTestDbColumn("unIqueidentifier"), "UNIQUEIDENTIFIER"};
yield return new object[] {new FormatterTestDbColumn("sqL_variant"), "SQL_VARIANT"};
yield return new object[] {new FormatterTestDbColumn("somEthing.sys.hierarchyid"), "SOMETHING.SYS.HIERARCHYID"};
yield return new object[] {new FormatterTestDbColumn("geOgraphy"), "GEOGRAPHY"};
yield return new object[] {new FormatterTestDbColumn("geOmetry"), "GEOMETRY"};
yield return new object[] {new FormatterTestDbColumn("sySname"), "SYSNAME"};
yield return new object[] {new FormatterTestDbColumn("tiMestamp"), "TIMESTAMP"};
}
} }
[Theory]
[MemberData(nameof(FormatColumnTypeData))]
public void FormatColumnType(DbColumn input, string expectedOutput)
{
// If: I supply the input columns
string output = ToSqlScript.FormatColumnType(input);
// Then: The output should match the expected output
Assert.Equal(expectedOutput, output);
}
#endregion
private class FormatterTestDbColumn : DbColumn private class FormatterTestDbColumn : DbColumn
{ {
public FormatterTestDbColumn(string dataType, int? precision = null, int? scale = null) public FormatterTestDbColumn(string dataType, int? precision = null, int? scale = null, int? size = null)
{ {
DataTypeName = dataType; DataTypeName = dataType;
NumericPrecision = precision; NumericPrecision = precision;
NumericScale = scale; NumericScale = scale;
ColumnSize = size;
} }
} }
} }