diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
index 482c615e..c9e652a1 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/EditSession.cs
@@ -167,31 +167,10 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
throw new InvalidOperationException(SR.EditDataFailedAddRow);
}
- // Set the default values of the row if we know them
- string[] defaultValues = new string[objectMetadata.Columns.Length];
- for(int i = 0; i < objectMetadata.Columns.Length; i++)
- {
- EditColumnMetadata col = objectMetadata.Columns[i];
-
- // If the column is calculated, return the calculated placeholder as the display value
- if (col.IsCalculated.HasTrue())
- {
- defaultValues[i] = SR.EditDataComputedColumnPlaceholder;
- }
- else
- {
- if (col.DefaultValue != null)
- {
- newRow.SetCell(i, col.DefaultValue);
- }
- defaultValues[i] = col.DefaultValue;
- }
- }
-
EditCreateRowResult output = new EditCreateRowResult
{
- NewRowId = newRowId,
- DefaultValues = defaultValues
+ NewRowId = newRow.RowId,
+ DefaultValues = newRow.DefaultValues
};
return output;
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
index 70ae9f76..35d8e561 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowCreate.cs
@@ -9,6 +9,7 @@ using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
@@ -23,9 +24,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
///
public sealed class RowCreate : RowEditBase
{
- private const string InsertStart = "INSERT INTO {0}({1})";
- private const string InsertCompleteScript = "{0} VALUES ({1})";
- private const string InsertCompleteOutput = "{0} OUTPUT {1} VALUES ({2})";
+ private const string InsertScriptStart = "INSERT INTO {0}";
+ private const string InsertScriptColumns = "({0})";
+ private const string InsertScriptOut = " OUTPUT {0}";
+ private const string InsertScriptDefault = " DEFAULT VALUES";
+ private const string InsertScriptValues = " VALUES ({0})";
internal readonly CellUpdate[] newCells;
@@ -39,6 +42,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
: base(rowId, associatedResultSet, associatedMetadata)
{
newCells = new CellUpdate[associatedResultSet.Columns.Length];
+
+ // Process the default cell values. If the column is calculated, then the value is a placeholder
+ DefaultValues = associatedMetadata.Columns.Select((col, index) => col.IsCalculated.HasTrue()
+ ? SR.EditDataComputedColumnPlaceholder
+ : col.DefaultValue).ToArray();
}
///
@@ -47,6 +55,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
///
protected override int SortId => 1;
+ ///
+ /// Default values for the row, will be applied as cell updates if there isn't a user-
+ /// provided cell update during commit
+ ///
+ public string[] DefaultValues { get; }
+
#region Public Methods
///
@@ -74,50 +88,13 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
{
Validate.IsNotNull(nameof(connection), connection);
- // Process all the columns. Add the column to the output columns, add updateable
- // columns to the input parameters
- List outColumns = new List();
- List inColumns = new List();
- DbCommand command = connection.CreateCommand();
- for (int i = 0; i < AssociatedResultSet.Columns.Length; i++)
- {
- DbColumnWrapper column = AssociatedResultSet.Columns[i];
- CellUpdate cell = newCells[i];
-
- // Add the column to the output
- outColumns.Add($"inserted.{SqlScriptFormatter.FormatIdentifier(column.ColumnName)}");
-
- // Skip columns that cannot be updated
- if (!column.IsUpdatable)
- {
- continue;
- }
-
- // If we're missing a cell, then we cannot continue
- if (cell == null)
- {
- throw new InvalidOperationException(SR.EditDataCreateScriptMissingValue);
- }
-
- // Create a parameter for the value and add it to the command
- // Add the parameterization to the list and add it to the command
- string paramName = $"@Value{RowId}{i}";
- inColumns.Add(paramName);
- SqlParameter param = new SqlParameter(paramName, cell.Column.SqlDbType)
- {
- Value = cell.Value
- };
- command.Parameters.Add(param);
- }
- string joinedInColumns = string.Join(", ", inColumns);
- string joinedOutColumns = string.Join(", ", outColumns);
+ // Build the script and generate a command
+ ScriptBuildResult result = BuildInsertScript(forCommand: true);
- // Get the start clause
- string start = GetTableClause();
-
- // Put the whole #! together
- command.CommandText = string.Format(InsertCompleteOutput, start, joinedOutColumns, joinedInColumns);
+ DbCommand command = connection.CreateCommand();
+ command.CommandText = result.ScriptText;
command.CommandType = CommandType.Text;
+ command.Parameters.AddRange(result.ScriptParameters);
return command;
}
@@ -129,15 +106,9 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// EditRow of pending update
public override EditRow GetEditRow(DbCellValue[] cachedRow)
{
- // Iterate over the new cells. If they are null, generate a blank value
- EditCell[] editCells = newCells.Select(cell =>
- {
- DbCellValue dbCell = cell == null
- ? new DbCellValue {DisplayValue = string.Empty, IsNull = false, RawObject = null}
- : cell.AsDbCellValue;
- return new EditCell(dbCell, true);
- })
- .ToArray();
+ // Get edit cells for each
+ EditCell[] editCells = newCells.Select(GetEditCell).ToArray();
+
return new EditRow
{
Id = RowId,
@@ -152,35 +123,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
/// INSERT INTO statement
public override string GetScript()
{
- // Process all the cells, and generate the values
- List values = new List();
- for (int i = 0; i < AssociatedResultSet.Columns.Length; i++)
- {
- DbColumnWrapper column = AssociatedResultSet.Columns[i];
- CellUpdate cell = newCells[i];
-
- // Skip columns that cannot be updated
- if (!column.IsUpdatable)
- {
- continue;
- }
-
- // If we're missing a cell, then we cannot continue
- if (cell == null)
- {
- throw new InvalidOperationException(SR.EditDataCreateScriptMissingValue);
- }
-
- // Format the value and add it to the list
- values.Add(SqlScriptFormatter.FormatValue(cell.Value, column));
- }
- string joinedValues = string.Join(", ", values);
-
- // Get the start clause
- string start = GetTableClause();
-
- // Put the whole #! together
- return string.Format(InsertCompleteScript, start, joinedValues);
+ return BuildInsertScript(forCommand: false).ScriptText;
}
///
@@ -195,9 +138,11 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
// Remove the cell update from list of set cells
newCells[columnId] = null;
- return new EditRevertCellResult {IsRowDirty = true, Cell = null};
- // @TODO: Return default value when we have support checked in
- // @TODO: RETURN THE DEFAULT VALUE
+ return new EditRevertCellResult
+ {
+ IsRowDirty = true,
+ Cell = GetEditCell(null, columnId)
+ };
}
///
@@ -227,16 +172,140 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
#endregion
- private string GetTableClause()
- {
- // Get all the columns that will be provided
- var inColumns = from c in AssociatedResultSet.Columns
- where c.IsUpdatable
- select SqlScriptFormatter.FormatIdentifier(c.ColumnName);
+ ///
+ /// Generates an INSERT script that will insert this row
+ ///
+ ///
+ /// If true the script will be generated with an OUTPUT clause for returning all
+ /// values in the inserted row (including computed values). The script will also generate
+ /// parameters for inserting the values.
+ /// If false the script will not have an OUTPUT clause and will have the values
+ /// directly inserted into the script (with proper escaping, of course).
+ ///
+ /// A script build result object with the script text and any parameters
+ ///
+ /// Thrown if there are columns that are not readonly, do not have default values, and were
+ /// not assigned values.
+ ///
+ private ScriptBuildResult BuildInsertScript(bool forCommand)
+ {
+ // Process all the columns in this table
+ List inValues = new List();
+ List inColumns = new List();
+ List outColumns = new List();
+ List sqlParameters = new List();
+ for (int i = 0; i < AssociatedObjectMetadata.Columns.Length; i++)
+ {
+ DbColumnWrapper column = AssociatedResultSet.Columns[i];
+ CellUpdate cell = newCells[i];
+
+ // Add an out column if we're doing this for a command
+ if (forCommand)
+ {
+ outColumns.Add($"inserted.{SqlScriptFormatter.FormatIdentifier(column.ColumnName)}");
+ }
+
+ // Skip columns that cannot be updated
+ if (!column.IsUpdatable)
+ {
+ continue;
+ }
+
+ // Make sure a value was provided for the cell
+ if (cell == null)
+ {
+ // If there isn't a default, then fail
+ if (DefaultValues[i] == null)
+ {
+ throw new InvalidOperationException(SR.EditDataCreateScriptMissingValue);
+ }
+
+ // There is a default value, so trust the db will apply it
+ continue;
+ }
- // Package it into a single INSERT statement starter
- string inColumnsJoined = string.Join(", ", inColumns);
- return string.Format(InsertStart, AssociatedObjectMetadata.EscapedMultipartName, inColumnsJoined);
+ // Add the input values
+ if (forCommand)
+ {
+ // Since this script is for command use, add parameter for the input value to the list
+ string paramName = $"@Value{RowId}_{i}";
+ inValues.Add(paramName);
+
+ SqlParameter param = new SqlParameter(paramName, cell.Column.SqlDbType) {Value = cell.Value};
+ sqlParameters.Add(param);
+ }
+ else
+ {
+ // This script isn't for command use, add the value, formatted for insertion
+ inValues.Add(SqlScriptFormatter.FormatValue(cell.Value, column));
+ }
+
+ // Add the column to the in columns
+ inColumns.Add(SqlScriptFormatter.FormatIdentifier(column.ColumnName));
+ }
+
+ // Begin the script (ie, INSERT INTO blah)
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.AppendFormat(InsertScriptStart, AssociatedObjectMetadata.EscapedMultipartName);
+
+ // Add the input columns (if there are any)
+ if (inColumns.Count > 0)
+ {
+ string joinedInColumns = string.Join(", ", inColumns);
+ queryBuilder.AppendFormat(InsertScriptColumns, joinedInColumns);
+ }
+
+ // Add the output columns (this will be empty if we are not building for command)
+ if (outColumns.Count > 0)
+ {
+ string joinedOutColumns = string.Join(", ", outColumns);
+ queryBuilder.AppendFormat(InsertScriptOut, joinedOutColumns);
+ }
+
+ // Add the input values (if there any) or use the default values
+ if (inValues.Count > 0)
+ {
+ string joinedInValues = string.Join(", ", inValues);
+ queryBuilder.AppendFormat(InsertScriptValues, joinedInValues);
+ }
+ else
+ {
+ queryBuilder.AppendFormat(InsertScriptDefault);
+ }
+
+ return new ScriptBuildResult
+ {
+ ScriptText = queryBuilder.ToString(),
+ ScriptParameters = sqlParameters.ToArray()
+ };
+ }
+
+ private EditCell GetEditCell(CellUpdate cell, int index)
+ {
+ DbCellValue dbCell;
+ if (cell == null)
+ {
+ // Cell hasn't been provided by user yet, attempt to use the default value
+ dbCell = new DbCellValue
+ {
+ DisplayValue = DefaultValues[index] ?? string.Empty,
+ IsNull = false, // TODO: This doesn't properly consider null defaults
+ RawObject = null,
+ RowId = RowId
+ };
+ }
+ else
+ {
+ // Cell has been provided by user, so use that
+ dbCell = cell.AsDbCellValue;
+ }
+ return new EditCell(dbCell, isDirty: true);
+ }
+
+ private class ScriptBuildResult
+ {
+ public string ScriptText { get; set; }
+ public SqlParameter[] ScriptParameters { get; set; }
}
}
}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
index db377e2c..491fe5e0 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/EditData/UpdateManagement/RowUpdate.cs
@@ -82,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
foreach (var updateElement in cellUpdates)
{
string formattedColumnName = SqlScriptFormatter.FormatIdentifier(updateElement.Value.Column.ColumnName);
- string paramName = $"@Value{RowId}{updateElement.Key}";
+ string paramName = $"@Value{RowId}_{updateElement.Key}";
setComponents.Add($"{formattedColumnName} = {paramName}");
SqlParameter parameter = new SqlParameter(paramName, updateElement.Value.Column.SqlDbType)
{
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs
index 36a73b1b..1f55976d 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/Common.cs
@@ -8,6 +8,7 @@ using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Castle.Components.DictionaryAdapter;
using Microsoft.SqlTools.ServiceLayer.EditData;
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
@@ -22,6 +23,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
public class Common
{
public const string OwnerUri = "testFile";
+ public const string DefaultValue = "defaultValue";
+ public const string TableName = "tbl";
public static EditInitializeParams BasicInitializeParameters
{
@@ -62,17 +65,14 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
return session;
}
- public static EditTableMetadata GetStandardMetadata(DbColumn[] columns, bool isMemoryOptimized = false)
+ public static EditTableMetadata GetStandardMetadata(DbColumn[] columns, bool isMemoryOptimized = false, int defaultColumns = 0)
{
- // Create column metadata providers
- var columnMetas = columns.Select((c, i) =>
+ // Create column metadata providers
+ var columnMetas = columns.Select((c, i) => new EditColumnMetadata
{
- var ecm = new EditColumnMetadata
- {
- EscapedName = c.ColumnName,
- Ordinal = i
- };
- return ecm;
+ EscapedName = c.ColumnName,
+ Ordinal = i,
+ DefaultValue = i < defaultColumns ? DefaultValue : null
}).ToArray();
// Create column wrappers
@@ -82,7 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
EditTableMetadata editTableMetadata = new EditTableMetadata
{
Columns = columnMetas,
- EscapedMultipartName = "tbl",
+ EscapedMultipartName = TableName,
IsMemoryOptimized = isMemoryOptimized
};
editTableMetadata.Extend(columnWrappers);
@@ -133,11 +133,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
return new TestDbDataReader(new [] {testResultSet}, false);
}
- public static void AddCells(RowEditBase rc, bool includeIdentity)
+ public static void AddCells(RowEditBase rc, int colsToSkip)
{
// Skip the first column since if identity, since identity columns can't be updated
- int start = includeIdentity ? 1 : 0;
- for (int i = start; i < rc.AssociatedResultSet.Columns.Length; i++)
+ for (int i = colsToSkip; i < rc.AssociatedResultSet.Columns.Length; i++)
{
rc.SetCell(i, "123");
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs
index dc3e6ed9..1e916d26 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/RowCreateTests.cs
@@ -4,7 +4,9 @@
//
using System;
+using System.Collections.Generic;
using System.Data.Common;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -38,39 +40,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
Assert.Equal(etm, rc.AssociatedObjectMetadata);
}
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public async Task GetScript(bool includeIdentity)
- {
- // Setup: Generate the parameters for the row create
- const long rowId = 100;
- DbColumn[] columns = Common.GetColumns(includeIdentity);
- ResultSet rs = await Common.GetResultSet(columns, includeIdentity);
- EditTableMetadata etm = Common.GetStandardMetadata(columns);
-
- // If: I ask for a script to be generated without an identity column
- RowCreate rc = new RowCreate(rowId, rs, etm);
- Common.AddCells(rc, includeIdentity);
- string script = rc.GetScript();
-
- // Then:
- // ... The script should not be null,
- Assert.NotNull(script);
-
- // ... It should be formatted as an insert script
- Regex r = new Regex(@"INSERT INTO (.+)\((.*)\) VALUES \((.*)\)");
- var m = r.Match(script);
- Assert.True(m.Success);
-
- // ... It should have 3 columns and 3 values (regardless of the presence of an identity col)
- string tbl = m.Groups[1].Value;
- string cols = m.Groups[2].Value;
- string vals = m.Groups[3].Value;
- Assert.Equal(etm.EscapedMultipartName, tbl);
- Assert.Equal(3, cols.Split(',').Length);
- Assert.Equal(3, vals.Split(',').Length);
- }
+ #region GetScript Tests
[Fact]
public async Task GetScriptMissingCell()
@@ -83,6 +53,82 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
Assert.Throws(() => rc.GetScript());
}
+ public static IEnumerable