mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-31 17:24:37 -05:00
Remove SELECT * from edit/initialize Query (#288)
* Major refactor of EditDataMetadata providers * EditMetadataFactory generates "basic" EditTableMetadata objects based entirely on SMO metadata * SmoEditTableMetadata no longer depends on SMO, making it unecessary to mock it * Renamed SmoEditTableMetadata to EditTableMetadata * EditTableMetadata can be extended with DbColumnWrappers * Moving logic for extending a EditColumnMetadata into that class * I *think* this will work for async execution of initialize tasks * Fixing unit tests for new Edit(Table|Column)Metadata classes * Async stuff that works! And passes unit tests * Adding unit tests Adding .idea to gitignore * Adding message to the EditSessionReadyEvent * Fixes from dev merge * Fixing unit tests that Rider didn't catch as failing May have been a bit heavy-handed with the async/await stuff * Couple changes as per PR comments
This commit is contained in:
@@ -14,6 +14,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.Contracts
|
||||
/// </summary>
|
||||
public string OwnerUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message to explain why a session failed. Should only be set when <see cref="Success"/>
|
||||
/// is <c>false</c>.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the session is ready
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Data;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
/// <summary>
|
||||
/// Small class that stores information needed by the edit data service to properly process
|
||||
/// edits into scripts.
|
||||
/// </summary>
|
||||
public class EditColumnMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a simple edit column metadata provider
|
||||
/// </summary>
|
||||
public EditColumnMetadata()
|
||||
{
|
||||
HasExtendedProperties = false;
|
||||
}
|
||||
|
||||
#region Basic Properties (properties provided by SMO)
|
||||
|
||||
/// <summary>
|
||||
/// If set, this is a string representation of the default value. If set to null, then the
|
||||
/// column does not have a default value.
|
||||
/// </summary>
|
||||
public string DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Escaped identifier for the name of the column
|
||||
/// </summary>
|
||||
public string EscapedName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is computed
|
||||
/// </summary>
|
||||
public bool IsComputed { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is deterministically computed
|
||||
/// </summary>
|
||||
public bool IsDeterministic { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is an identity column
|
||||
/// </summary>
|
||||
public bool IsIdentity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ordinal ID of the column
|
||||
/// </summary>
|
||||
public int Ordinal { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended Properties (properties provided by SqlClient)
|
||||
|
||||
public DbColumnWrapper DbColumn { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column has extended properties
|
||||
/// </summary>
|
||||
public bool HasExtendedProperties { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is calculated on the server side. This could be a computed
|
||||
/// column or a identity column.
|
||||
/// </summary>
|
||||
public bool? IsCalculated { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is used in a key to uniquely identify a row
|
||||
/// </summary>
|
||||
public bool? IsKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column can be trusted for uniqueness
|
||||
/// </summary>
|
||||
public bool? IsTrustworthyForUniqueness { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Extracts extended column properties from the database columns from SQL Client
|
||||
/// </summary>
|
||||
/// <param name="dbColumn">The column information provided by SQL Client</param>
|
||||
public void Extend(DbColumnWrapper dbColumn)
|
||||
{
|
||||
Validate.IsNotNull(nameof(dbColumn), dbColumn);
|
||||
|
||||
DbColumn = dbColumn;
|
||||
|
||||
// A column is trustworthy for uniqueness if it can be updated or it has an identity
|
||||
// property. If both of these are false (eg, timestamp) we can't trust it to uniquely
|
||||
// identify a row in the table
|
||||
IsTrustworthyForUniqueness = dbColumn.IsUpdatable || dbColumn.IsIdentity.HasTrue();
|
||||
|
||||
// A key column is determined by whether it is a key
|
||||
IsKey = dbColumn.IsKey;
|
||||
|
||||
// A column is calculated if it is identity, computed, or otherwise not updatable
|
||||
IsCalculated = IsIdentity || IsComputed || !dbColumn.IsUpdatable;
|
||||
|
||||
// Mark the column as extended
|
||||
HasExtendedProperties = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
/// <summary>
|
||||
/// Small class that stores information needed by the edit data service to properly process
|
||||
/// edits into scripts.
|
||||
/// </summary>
|
||||
public class EditColumnWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// The DB column
|
||||
/// </summary>
|
||||
public DbColumnWrapper DbColumn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If set, this is a string representation of the default value. If set to null, then the
|
||||
/// column does not have a default value.
|
||||
/// </summary>
|
||||
public string DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Escaped identifier for the name of the column
|
||||
/// </summary>
|
||||
public string EscapedName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is calculated on the server side. This could be a computed
|
||||
/// column or a identity column.
|
||||
/// </summary>
|
||||
public bool IsCalculated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column is used in a key to uniquely identify a row
|
||||
/// </summary>
|
||||
public bool IsKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the column can be trusted for uniqueness
|
||||
/// </summary>
|
||||
public bool IsTrustworthyForUniqueness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ordinal ID of the column
|
||||
/// </summary>
|
||||
public int Ordinal { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts.ExecuteRequests;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using ConnectionType = Microsoft.SqlTools.ServiceLayer.Connection.ConnectionType;
|
||||
|
||||
@@ -57,10 +56,6 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
private readonly Lazy<ConcurrentDictionary<string, EditSession>> editSessions = new Lazy<ConcurrentDictionary<string, EditSession>>(
|
||||
() => new ConcurrentDictionary<string, EditSession>());
|
||||
|
||||
private readonly Lazy<ConcurrentDictionary<string, TaskCompletionSource<bool>>> initializeWaitHandles =
|
||||
new Lazy<ConcurrentDictionary<string, TaskCompletionSource<bool>>>(
|
||||
() => new ConcurrentDictionary<string, TaskCompletionSource<bool>>());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
@@ -70,12 +65,6 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// </summary>
|
||||
internal ConcurrentDictionary<string, EditSession> ActiveSessions => editSessions.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary mapping OwnerURIs to wait handlers for initialize tasks. Pretty much only
|
||||
/// provided for unit test scenarios.
|
||||
/// </summary>
|
||||
internal ConcurrentDictionary<string, TaskCompletionSource<bool>> InitializeWaitHandles => initializeWaitHandles.Value;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -159,63 +148,32 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
internal async Task HandleInitializeRequest(EditInitializeParams initParams,
|
||||
RequestContext<EditInitializeResult> requestContext)
|
||||
{
|
||||
Func<Exception, Task> executionFailureHandler = (e) => SendSessionReadyEvent(requestContext, initParams.OwnerUri, false, e.Message);
|
||||
Func<Task> executionSuccessHandler = () => SendSessionReadyEvent(requestContext, initParams.OwnerUri, true, null);
|
||||
|
||||
EditSession.Connector connector = () => connectionService.GetOrOpenConnection(initParams.OwnerUri, ConnectionType.Edit);
|
||||
EditSession.QueryRunner queryRunner = q => SessionInitializeQueryRunner(initParams.OwnerUri, requestContext, q);
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
// Make sure we have info to process this request
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(initParams.OwnerUri), initParams.OwnerUri);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(initParams.ObjectName), initParams.ObjectName);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(initParams.ObjectType), initParams.ObjectType);
|
||||
|
||||
// Try to add a new wait handler to the
|
||||
if (!InitializeWaitHandles.TryAdd(initParams.OwnerUri, new TaskCompletionSource<bool>()))
|
||||
// Create a session and add it to the session list
|
||||
EditSession session = new EditSession(metadataFactory, initParams.ObjectName, initParams.ObjectType);
|
||||
if (!ActiveSessions.TryAdd(initParams.OwnerUri, session))
|
||||
{
|
||||
throw new InvalidOperationException(SR.EditDataInitializeInProgress);
|
||||
throw new InvalidOperationException(SR.EditDataSessionAlreadyExists);
|
||||
}
|
||||
|
||||
// Setup a callback for when the query has successfully created
|
||||
Func<Query, Task<bool>> queryCreateSuccessCallback = async query =>
|
||||
{
|
||||
await requestContext.SendResult(new EditInitializeResult());
|
||||
return true;
|
||||
};
|
||||
|
||||
// Setup a callback for when the query failed to be created
|
||||
Func<string, Task> queryCreateFailureCallback = async message =>
|
||||
{
|
||||
await requestContext.SendError(message);
|
||||
CompleteInitializeWaitHandler(initParams.OwnerUri, false);
|
||||
};
|
||||
|
||||
// Setup a callback for when the query completes execution successfully
|
||||
Query.QueryAsyncEventHandler queryCompleteSuccessCallback =
|
||||
q => QueryCompleteCallback(q, initParams, requestContext);
|
||||
|
||||
// Setup a callback for when the query completes execution with failure
|
||||
Query.QueryAsyncEventHandler queryCompleteFailureCallback = async query =>
|
||||
{
|
||||
EditSessionReadyParams readyParams = new EditSessionReadyParams
|
||||
{
|
||||
OwnerUri = initParams.OwnerUri,
|
||||
Success = false
|
||||
};
|
||||
await requestContext.SendEvent(EditSessionReadyEvent.Type, readyParams);
|
||||
CompleteInitializeWaitHandler(initParams.OwnerUri, false);
|
||||
};
|
||||
|
||||
// Put together a query for the results and execute it
|
||||
ExecuteStringParams executeParams = new ExecuteStringParams
|
||||
{
|
||||
Query = $"SELECT * FROM {SqlScriptFormatter.FormatMultipartIdentifier(initParams.ObjectName)}",
|
||||
OwnerUri = initParams.OwnerUri
|
||||
};
|
||||
await queryExecutionService.InterServiceExecuteQuery(executeParams, requestContext,
|
||||
queryCreateSuccessCallback, queryCreateFailureCallback,
|
||||
queryCompleteSuccessCallback, queryCompleteFailureCallback);
|
||||
// Initialize the session
|
||||
session.Initialize(connector, queryRunner, executionSuccessHandler, executionFailureHandler);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await requestContext.SendError(e.Message);
|
||||
CompleteInitializeWaitHandler(initParams.OwnerUri, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,52 +271,64 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
return editSession;
|
||||
}
|
||||
|
||||
private async Task QueryCompleteCallback(Query query, EditInitializeParams initParams,
|
||||
IEventSender requestContext)
|
||||
private async Task<EditSession.EditSessionQueryExecutionState> SessionInitializeQueryRunner(string ownerUri,
|
||||
IEventSender eventSender, string query)
|
||||
{
|
||||
EditSessionReadyParams readyParams = new EditSessionReadyParams
|
||||
// Open a task completion source, effectively creating a synchronous block
|
||||
TaskCompletionSource<EditSession.EditSessionQueryExecutionState> taskCompletion =
|
||||
new TaskCompletionSource<EditSession.EditSessionQueryExecutionState>();
|
||||
|
||||
// Setup callback for successful query creation
|
||||
// NOTE: We do not want to set the task completion source, since we will continue executing the query after
|
||||
Func<Query, Task<bool>> queryCreateSuccessCallback = q => Task.FromResult(true);
|
||||
|
||||
// Setup callback for failed query creation
|
||||
Func<string, Task> queryCreateFailureCallback = m =>
|
||||
{
|
||||
OwnerUri = initParams.OwnerUri
|
||||
taskCompletion.SetResult(new EditSession.EditSessionQueryExecutionState(null, m));
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
try
|
||||
// Setup callback for successful query execution
|
||||
Query.QueryAsyncEventHandler queryCompleteSuccessCallback = q =>
|
||||
{
|
||||
// Validate the query for a editSession
|
||||
ResultSet resultSet = EditSession.ValidateQueryForSession(query);
|
||||
taskCompletion.SetResult(new EditSession.EditSessionQueryExecutionState(q));
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Get a connection we'll use for SMO metadata lookup (and committing, later on)
|
||||
DbConnection conn = await connectionService.GetOrOpenConnection(initParams.OwnerUri, ConnectionType.Edit);
|
||||
var metadata = metadataFactory.GetObjectMetadata(conn, resultSet.Columns,
|
||||
initParams.ObjectName, initParams.ObjectType);
|
||||
|
||||
// Create the editSession and add it to the sessions list
|
||||
EditSession editSession = new EditSession(resultSet, metadata);
|
||||
if (!ActiveSessions.TryAdd(initParams.OwnerUri, editSession))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create edit editSession, editSession already exists.");
|
||||
}
|
||||
readyParams.Success = true;
|
||||
}
|
||||
catch (Exception)
|
||||
// Setup callback for failed query execution
|
||||
Query.QueryAsyncEventHandler queryCompleteFailureCallback = q =>
|
||||
{
|
||||
// Request that the query be disposed
|
||||
await queryExecutionService.InterServiceDisposeQuery(initParams.OwnerUri, null, null);
|
||||
readyParams.Success = false;
|
||||
}
|
||||
taskCompletion.SetResult(new EditSession.EditSessionQueryExecutionState(null));
|
||||
return Task.FromResult(0);
|
||||
};
|
||||
|
||||
// Send the edit session ready notification
|
||||
await requestContext.SendEvent(EditSessionReadyEvent.Type, readyParams);
|
||||
CompleteInitializeWaitHandler(initParams.OwnerUri, true);
|
||||
// Execute the query
|
||||
ExecuteStringParams executeParams = new ExecuteStringParams
|
||||
{
|
||||
Query = query,
|
||||
OwnerUri = ownerUri
|
||||
};
|
||||
await queryExecutionService.InterServiceExecuteQuery(executeParams, eventSender,
|
||||
queryCreateSuccessCallback, queryCreateFailureCallback,
|
||||
queryCompleteSuccessCallback, queryCompleteFailureCallback);
|
||||
|
||||
// Wait for the completion source to complete, this will wait until the query has
|
||||
// completed and sent all its events.
|
||||
return await taskCompletion.Task;
|
||||
}
|
||||
|
||||
private void CompleteInitializeWaitHandler(string ownerUri, bool result)
|
||||
private static Task SendSessionReadyEvent(IEventSender eventSender, string ownerUri, bool success,
|
||||
string message)
|
||||
{
|
||||
// If there isn't a wait handler, just ignore it
|
||||
TaskCompletionSource<bool> initializeWaiter;
|
||||
if (ownerUri != null && InitializeWaitHandles.TryRemove(ownerUri, out initializeWaiter))
|
||||
var sessionReadyParams = new EditSessionReadyParams
|
||||
{
|
||||
initializeWaiter.SetResult(result);
|
||||
}
|
||||
OwnerUri = ownerUri,
|
||||
Message = message,
|
||||
Success = success
|
||||
};
|
||||
|
||||
return eventSender.SendEvent(EditSessionReadyEvent.Type, sessionReadyParams);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -25,28 +25,37 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
public class EditSession
|
||||
{
|
||||
|
||||
private readonly ResultSet associatedResultSet;
|
||||
private readonly IEditTableMetadata objectMetadata;
|
||||
private ResultSet associatedResultSet;
|
||||
|
||||
private readonly IEditMetadataFactory metadataFactory;
|
||||
private EditTableMetadata objectMetadata;
|
||||
private readonly string objectName;
|
||||
private readonly string objectType;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new edit session bound to the result set and metadat object provided
|
||||
/// </summary>
|
||||
/// <param name="resultSet">The result set of the table to be edited</param>
|
||||
/// <param name="objMetadata">Metadata provider for the table to be edited</param>
|
||||
public EditSession(ResultSet resultSet, IEditTableMetadata objMetadata)
|
||||
/// <param name="metaFactory">Factory for creating metadata</param>
|
||||
/// <param name="objName">The name of the object to edit</param>
|
||||
/// <param name="objType">The type of the object to edit</param>
|
||||
public EditSession(IEditMetadataFactory metaFactory, string objName, string objType)
|
||||
{
|
||||
Validate.IsNotNull(nameof(resultSet), resultSet);
|
||||
Validate.IsNotNull(nameof(objMetadata), objMetadata);
|
||||
Validate.IsNotNull(nameof(metaFactory), metaFactory);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(objName), objName);
|
||||
Validate.IsNotNullOrWhitespaceString(nameof(objType), objType);
|
||||
|
||||
// Setup the internal state
|
||||
associatedResultSet = resultSet;
|
||||
objectMetadata = objMetadata;
|
||||
NextRowId = associatedResultSet.RowCount;
|
||||
EditCache = new ConcurrentDictionary<long, RowEditBase>();
|
||||
metadataFactory = metaFactory;
|
||||
objectName = objName;
|
||||
objectType = objType;
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
public delegate Task<DbConnection> Connector();
|
||||
|
||||
public delegate Task<EditSessionQueryExecutionState> QueryRunner(string query);
|
||||
|
||||
/// <summary>
|
||||
/// The task that is running to commit the changes to the db
|
||||
/// Internal for unit test purposes.
|
||||
@@ -61,12 +70,43 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <summary>
|
||||
/// The cache of pending updates. Internal for unit test purposes only
|
||||
/// </summary>
|
||||
internal ConcurrentDictionary<long, RowEditBase> EditCache { get; }
|
||||
internal ConcurrentDictionary<long, RowEditBase> EditCache { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The task that is running to initialize the edit session
|
||||
/// </summary>
|
||||
internal Task InitializeTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the session has been initialized
|
||||
/// </summary>
|
||||
public bool IsInitialized { get; internal set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Initialize(Connector connector, QueryRunner queryRunner, Func<Task> successHandler, Func<Exception, Task> errorHandler)
|
||||
{
|
||||
if (IsInitialized)
|
||||
{
|
||||
throw new InvalidOperationException(SR.EditDataSessionAlreadyInitialized);
|
||||
}
|
||||
|
||||
if (InitializeTask != null)
|
||||
{
|
||||
throw new InvalidOperationException(SR.EditDataSessionAlreadyInitializing);
|
||||
}
|
||||
|
||||
Validate.IsNotNull(nameof(connector), connector);
|
||||
Validate.IsNotNull(nameof(queryRunner), queryRunner);
|
||||
Validate.IsNotNull(nameof(successHandler), successHandler);
|
||||
Validate.IsNotNull(nameof(errorHandler), errorHandler);
|
||||
|
||||
// Start up the initialize process
|
||||
InitializeTask = InitializeInternal(connector, queryRunner, successHandler, errorHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates that a query can be used for an edit session. The target result set is returned
|
||||
/// </summary>
|
||||
@@ -100,6 +140,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <returns>The internal ID of the newly created row</returns>
|
||||
public EditCreateRowResult CreateRow()
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Create a new row ID (atomically, since this could be accesses concurrently)
|
||||
long newRowId = NextRowId++;
|
||||
|
||||
@@ -113,13 +155,13 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
}
|
||||
|
||||
// Set the default values of the row if we know them
|
||||
string[] defaultValues = new string[objectMetadata.Columns.Count];
|
||||
for(int i = 0; i < objectMetadata.Columns.Count; i++)
|
||||
string[] defaultValues = new string[objectMetadata.Columns.Length];
|
||||
for(int i = 0; i < objectMetadata.Columns.Length; i++)
|
||||
{
|
||||
EditColumnWrapper col = objectMetadata.Columns[i];
|
||||
EditColumnMetadata col = objectMetadata.Columns[i];
|
||||
|
||||
// If the column is calculated, return the calculated placeholder as the display value
|
||||
if (col.IsCalculated)
|
||||
if (col.IsCalculated.HasTrue())
|
||||
{
|
||||
defaultValues[i] = SR.EditDataComputedColumnPlaceholder;
|
||||
}
|
||||
@@ -150,6 +192,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <param name="errorHandler">Callback to perform if the commit process has failed at some point</param>
|
||||
public void CommitEdits(DbConnection connection, Func<Task> successHandler, Func<Exception, Task> errorHandler)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
Validate.IsNotNull(nameof(connection), connection);
|
||||
Validate.IsNotNull(nameof(successHandler), successHandler);
|
||||
Validate.IsNotNull(nameof(errorHandler), errorHandler);
|
||||
@@ -173,6 +217,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <param name="rowId">The internal ID of the row to delete</param>
|
||||
public void DeleteRow(long rowId)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Sanity check the row ID
|
||||
if (rowId >= NextRowId || rowId < 0)
|
||||
{
|
||||
@@ -196,6 +242,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <returns>An array of rows with pending edits applied</returns>
|
||||
public async Task<EditRow[]> GetRows(long startIndex, int rowCount)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Get the cached rows from the result set
|
||||
ResultSetSubset cachedRows = startIndex < associatedResultSet.RowCount
|
||||
? await associatedResultSet.GetSubset(startIndex, rowCount)
|
||||
@@ -249,6 +297,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <returns>String version of the old value for the cell</returns>
|
||||
public string RevertCell(long rowId, int columnId)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Attempt to get the row edit with the given ID
|
||||
RowEditBase pendingEdit;
|
||||
if (!EditCache.TryGetValue(rowId, out pendingEdit))
|
||||
@@ -269,6 +319,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <param name="rowId">The internal ID of the row to reset</param>
|
||||
public void RevertRow(long rowId)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Attempt to remove the row with the given ID
|
||||
RowEditBase removedEdit;
|
||||
if (!EditCache.TryRemove(rowId, out removedEdit))
|
||||
@@ -284,6 +336,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <returns></returns>
|
||||
public string ScriptEdits(string outputPath)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Validate the output path
|
||||
// @TODO: Reinstate this code once we have an interface around file generation
|
||||
//if (outputPath == null)
|
||||
@@ -328,6 +382,8 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// <param name="newValue">The new string value of the cell to update</param>
|
||||
public EditUpdateCellResult UpdateCell(long rowId, int columnId, string newValue)
|
||||
{
|
||||
ThrowIfNotInitialized();
|
||||
|
||||
// Sanity check to make sure that the row ID is in the range of possible values
|
||||
if (rowId >= NextRowId || rowId < 0)
|
||||
{
|
||||
@@ -347,6 +403,38 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
|
||||
#endregion
|
||||
|
||||
private async Task InitializeInternal(Connector connector, QueryRunner queryRunner,
|
||||
Func<Task> successHandler, Func<Exception, Task> failureHandler)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Step 1) Look up the SMO metadata
|
||||
objectMetadata = metadataFactory.GetObjectMetadata(await connector(), objectName, objectType);
|
||||
|
||||
// Step 2) Get and execute a query for the rows in the object we're looking up
|
||||
EditSessionQueryExecutionState state = await queryRunner(ConstructInitializeQuery());
|
||||
if (state.Query == null)
|
||||
{
|
||||
// TODO: Move to SR file
|
||||
string message = state.Message ?? SR.EditDataQueryFailed;
|
||||
throw new Exception(message);
|
||||
}
|
||||
|
||||
// Step 3) Setup the internal state
|
||||
associatedResultSet = ValidateQueryForSession(state.Query);
|
||||
NextRowId = associatedResultSet.RowCount;
|
||||
EditCache = new ConcurrentDictionary<long, RowEditBase>();
|
||||
IsInitialized = true;
|
||||
|
||||
// Step 4) Return our success
|
||||
await successHandler();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await failureHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CommitEditsInternal(DbConnection connection, Func<Task> successHandler, Func<Exception, Task> errorHandler)
|
||||
{
|
||||
try
|
||||
@@ -378,5 +466,50 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
await errorHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
private string ConstructInitializeQuery()
|
||||
{
|
||||
// Using the columns we know, put together a query for the rows in the table
|
||||
var columns = objectMetadata.Columns.Select(col => col.EscapedName);
|
||||
var columnClause = string.Join(", ", columns);
|
||||
|
||||
return $"SELECT ${columnClause} FROM ${objectMetadata.EscapedMultipartName}";
|
||||
}
|
||||
|
||||
private void ThrowIfNotInitialized()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
{
|
||||
throw new InvalidOperationException(SR.EditDataSessionNotInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State object to return upon completion of an edit session intialization query
|
||||
/// </summary>
|
||||
public class EditSessionQueryExecutionState
|
||||
{
|
||||
/// <summary>
|
||||
/// The query object that was used to execute the edit initialization query. If
|
||||
/// <c>null</c> the query was not successfully executed.
|
||||
/// </summary>
|
||||
public Query Query { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any message that may have occurred during execution of the query (ie, exceptions).
|
||||
/// If this is and <see cref="Query"/> are <c>null</c> then the error messages were
|
||||
/// returned via message events.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance. Sets the values of the properties.
|
||||
/// </summary>
|
||||
public EditSessionQueryExecutionState(Query query, string message = null)
|
||||
{
|
||||
Query = query;
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides metadata about the table or view being edited
|
||||
/// </summary>
|
||||
public class EditTableMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a simple edit table metadata provider
|
||||
/// </summary>
|
||||
public EditTableMetadata()
|
||||
{
|
||||
HasExtendedProperties = false;
|
||||
}
|
||||
|
||||
#region Basic Properties (properties provided by SMO)
|
||||
|
||||
/// <summary>
|
||||
/// List of columns in the object being edited
|
||||
/// </summary>
|
||||
public EditColumnMetadata[] Columns { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full escaped multipart identifier for the object being edited
|
||||
/// </summary>
|
||||
public string EscapedMultipartName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the object being edited is memory optimized
|
||||
/// </summary>
|
||||
public bool IsMemoryOptimized { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended Properties (properties provided by SqlClient)
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the table has had extended properties added to it
|
||||
/// </summary>
|
||||
public bool HasExtendedProperties { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of columns that are used to uniquely identify a row
|
||||
/// </summary>
|
||||
public EditColumnMetadata[] KeyColumns { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Extracts extended column properties from the database columns from SQL Client
|
||||
/// </summary>
|
||||
/// <param name="dbColumnWrappers">The column information provided by SQL Client</param>
|
||||
public void Extend(DbColumnWrapper[] dbColumnWrappers)
|
||||
{
|
||||
Validate.IsNotNull(nameof(dbColumnWrappers), dbColumnWrappers);
|
||||
|
||||
// Iterate over the column wrappers and improve the columns we have
|
||||
for (int i = 0; i < Columns.Length; i++)
|
||||
{
|
||||
Columns[i].Extend(dbColumnWrappers[i]);
|
||||
}
|
||||
|
||||
// Determine what the key columns are
|
||||
KeyColumns = Columns.Where(c => c.IsKey.HasTrue()).ToArray();
|
||||
if (KeyColumns.Length == 0)
|
||||
{
|
||||
// We didn't find any explicit key columns. Instead, we'll use all columns that are
|
||||
// trustworthy for uniqueness (usually all the columns)
|
||||
KeyColumns = Columns.Where(c => c.IsTrustworthyForUniqueness.HasTrue()).ToArray();
|
||||
}
|
||||
|
||||
// Mark that the table is now extended
|
||||
HasExtendedProperties = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
//
|
||||
|
||||
using System.Data.Common;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
@@ -17,10 +16,9 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// Generates a edit-ready metadata object
|
||||
/// </summary>
|
||||
/// <param name="connection">Connection to use for getting metadata</param>
|
||||
/// <param name="columns">List of columns from a query against the object</param>
|
||||
/// <param name="objectName">Name of the object to return metadata for</param>
|
||||
/// <param name="objectType">Type of the object to return metadata for</param>
|
||||
/// <returns>Metadata about the object requested</returns>
|
||||
IEditTableMetadata GetObjectMetadata(DbConnection connection, DbColumnWrapper[] columns, string objectName, string objectType);
|
||||
EditTableMetadata GetObjectMetadata(DbConnection connection, string objectName, string objectType);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface used in edit scenarios that defines properties for what columns are primary
|
||||
/// keys, and other metadata of the table.
|
||||
/// </summary>
|
||||
public interface IEditTableMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// All columns in the table that's being edited
|
||||
/// </summary>
|
||||
IReadOnlyList<EditColumnWrapper> Columns { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The escaped name of the table that's being edited
|
||||
/// </summary>
|
||||
string EscapedMultipartName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this table is a memory optimized table
|
||||
/// </summary>
|
||||
bool IsMemoryOptimized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Columns that can be used to uniquely identify the a row
|
||||
/// </summary>
|
||||
IReadOnlyList<EditColumnWrapper> KeyColumns { get; }
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,13 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.ServiceLayer.Connection.ReliableConnection;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
@@ -22,11 +23,10 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
/// Generates a edit-ready metadata object using SMO
|
||||
/// </summary>
|
||||
/// <param name="connection">Connection to use for getting metadata</param>
|
||||
/// <param name="columns">List of columns from a query against the object</param>
|
||||
/// <param name="objectName">Name of the object to return metadata for</param>
|
||||
/// <param name="objectType">Type of the object to return metadata for</param>
|
||||
/// <returns>Metadata about the object requested</returns>
|
||||
public IEditTableMetadata GetObjectMetadata(DbConnection connection, DbColumnWrapper[] columns, string objectName, string objectType)
|
||||
public EditTableMetadata GetObjectMetadata(DbConnection connection, string objectName, string objectType)
|
||||
{
|
||||
// Get a connection to the database for SMO purposes
|
||||
SqlConnection sqlConn = connection as SqlConnection;
|
||||
@@ -44,25 +44,59 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
sqlConn = reliableConn.GetUnderlyingConnection();
|
||||
}
|
||||
|
||||
// Connect with SMO and get the metadata for the table
|
||||
Server server = new Server(new ServerConnection(sqlConn));
|
||||
TableViewTableTypeBase result;
|
||||
TableViewTableTypeBase smoResult;
|
||||
switch (objectType.ToLowerInvariant())
|
||||
{
|
||||
case "table":
|
||||
result = server.Databases[sqlConn.Database].Tables[objectName];
|
||||
smoResult = server.Databases[sqlConn.Database].Tables[objectName];
|
||||
break;
|
||||
case "view":
|
||||
result = server.Databases[sqlConn.Database].Views[objectName];
|
||||
smoResult = server.Databases[sqlConn.Database].Views[objectName];
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(objectType), SR.EditDataUnsupportedObjectType(objectType));
|
||||
}
|
||||
if (result == null)
|
||||
if (smoResult == null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(objectName), SR.EditDataObjectMetadataNotFound);
|
||||
}
|
||||
|
||||
return new SmoEditTableMetadata(columns, result);
|
||||
// Generate the edit column metadata
|
||||
List<EditColumnMetadata> editColumns = new List<EditColumnMetadata>();
|
||||
for (int i = 0; i < smoResult.Columns.Count; i++)
|
||||
{
|
||||
Column smoColumn = smoResult.Columns[i];
|
||||
|
||||
// The default value may be escaped
|
||||
string defaultValue = smoColumn.DefaultConstraint == null
|
||||
? null
|
||||
: SqlScriptFormatter.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
|
||||
|
||||
EditColumnMetadata column = new EditColumnMetadata
|
||||
{
|
||||
DefaultValue = defaultValue,
|
||||
EscapedName = SqlScriptFormatter.FormatIdentifier(smoColumn.Name),
|
||||
Ordinal = i,
|
||||
};
|
||||
editColumns.Add(column);
|
||||
}
|
||||
|
||||
// Only tables can be memory-optimized
|
||||
Table smoTable = smoResult as Table;
|
||||
bool isMemoryOptimized = smoTable != null && smoTable.IsMemoryOptimized;
|
||||
|
||||
// Escape the parts of the name
|
||||
string[] objectNameParts = {smoResult.Schema, smoResult.Name};
|
||||
string escapedMultipartName = SqlScriptFormatter.FormatMultipartIdentifier(objectNameParts);
|
||||
|
||||
return new EditTableMetadata
|
||||
{
|
||||
Columns = editColumns.ToArray(),
|
||||
EscapedMultipartName = escapedMultipartName,
|
||||
IsMemoryOptimized = isMemoryOptimized,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.Utility;
|
||||
using Microsoft.SqlTools.ServiceLayer.Utility;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.EditData
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides metadata about the table or view being edited
|
||||
/// </summary>
|
||||
public class SmoEditTableMetadata : IEditTableMetadata
|
||||
{
|
||||
private readonly List<EditColumnWrapper> columns;
|
||||
private readonly List<EditColumnWrapper> keyColumns;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor that extracts useful metadata from the provided metadata objects
|
||||
/// </summary>
|
||||
/// <param name="dbColumns">DB columns from the ResultSet</param>
|
||||
/// <param name="smoObject">SMO metadata object for the table/view being edited</param>
|
||||
public SmoEditTableMetadata(IList<DbColumnWrapper> dbColumns, TableViewTableTypeBase smoObject)
|
||||
{
|
||||
Validate.IsNotNull(nameof(dbColumns), dbColumns);
|
||||
Validate.IsNotNull(nameof(smoObject), smoObject);
|
||||
|
||||
// Make sure that we have equal columns on both metadata providers
|
||||
Debug.Assert(dbColumns.Count == smoObject.Columns.Count);
|
||||
|
||||
// Create the columns for edit usage
|
||||
columns = new List<EditColumnWrapper>();
|
||||
for (int i = 0; i < dbColumns.Count; i++)
|
||||
{
|
||||
Column smoColumn = smoObject.Columns[i];
|
||||
DbColumnWrapper dbColumn = dbColumns[i];
|
||||
|
||||
// A column is trustworthy for uniqueness if it can be updated or it has an identity
|
||||
// property. If both of these are false (eg, timestamp) we can't trust it to uniquely
|
||||
// identify a row in the table
|
||||
bool isTrustworthyForUniqueness = dbColumn.IsUpdatable || smoColumn.Identity;
|
||||
|
||||
// The default value may be escaped
|
||||
string defaultValue = smoColumn.DefaultConstraint == null
|
||||
? null
|
||||
: SqlScriptFormatter.UnwrapLiteral(smoColumn.DefaultConstraint.Text);
|
||||
|
||||
EditColumnWrapper column = new EditColumnWrapper
|
||||
{
|
||||
DbColumn = dbColumn,
|
||||
Ordinal = i,
|
||||
DefaultValue = defaultValue,
|
||||
EscapedName = SqlScriptFormatter.FormatIdentifier(dbColumn.ColumnName),
|
||||
IsTrustworthyForUniqueness = isTrustworthyForUniqueness,
|
||||
|
||||
// A key column is determined by whether it is in the primary key and trustworthy
|
||||
IsKey = smoColumn.InPrimaryKey && isTrustworthyForUniqueness,
|
||||
|
||||
// A column is calculated if it is identity, computed, or otherwise not updatable
|
||||
IsCalculated = smoColumn.Identity || smoColumn.Computed || !dbColumn.IsUpdatable
|
||||
};
|
||||
columns.Add(column);
|
||||
}
|
||||
|
||||
// Determine what the key columns are
|
||||
keyColumns = columns.Where(c => c.IsKey).ToList();
|
||||
if (keyColumns.Count == 0)
|
||||
{
|
||||
// We didn't find any explicit key columns. Instead, we'll use all columns that are
|
||||
// trustworthy for uniqueness (usually all the columns)
|
||||
keyColumns = columns.Where(c => c.IsTrustworthyForUniqueness).ToList();
|
||||
}
|
||||
|
||||
// If a table is memory optimized it is Hekaton. If it's a view, then it can't be Hekaton
|
||||
Table smoTable = smoObject as Table;
|
||||
IsMemoryOptimized = smoTable != null && smoTable.IsMemoryOptimized;
|
||||
|
||||
// Escape the parts of the name
|
||||
string[] objectNameParts = {smoObject.Schema, smoObject.Name};
|
||||
EscapedMultipartName = SqlScriptFormatter.FormatMultipartIdentifier(objectNameParts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-only list of columns in the object being edited
|
||||
/// </summary>
|
||||
public IReadOnlyList<EditColumnWrapper> Columns => columns.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Full escaped multipart identifier for the object being edited
|
||||
/// </summary>
|
||||
public string EscapedMultipartName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the object being edited is memory optimized
|
||||
/// </summary>
|
||||
public bool IsMemoryOptimized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Read-only list of columns that are used to uniquely identify a row
|
||||
/// </summary>
|
||||
public IReadOnlyList<EditColumnWrapper> KeyColumns => keyColumns.AsReadOnly();
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <param name="rowId">Internal ID of the row that is being created</param>
|
||||
/// <param name="associatedResultSet">The result set for the rows in the table we're editing</param>
|
||||
/// <param name="associatedMetadata">The metadata for table we're editing</param>
|
||||
public RowCreate(long rowId, ResultSet associatedResultSet, IEditTableMetadata associatedMetadata)
|
||||
public RowCreate(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata)
|
||||
: base(rowId, associatedResultSet, associatedMetadata)
|
||||
{
|
||||
newCells = new CellUpdate[associatedResultSet.Columns.Length];
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <param name="rowId">Internal ID of the row to be deleted</param>
|
||||
/// <param name="associatedResultSet">Result set that is being edited</param>
|
||||
/// <param name="associatedMetadata">Improved metadata of the object being edited</param>
|
||||
public RowDelete(long rowId, ResultSet associatedResultSet, IEditTableMetadata associatedMetadata)
|
||||
public RowDelete(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata)
|
||||
: base(rowId, associatedResultSet, associatedMetadata)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <param name="rowId">The internal ID of the row that is being edited</param>
|
||||
/// <param name="associatedResultSet">The result set that will be updated</param>
|
||||
/// <param name="associatedMetadata">Metadata provider for the object to edit</param>
|
||||
protected RowEditBase(long rowId, ResultSet associatedResultSet, IEditTableMetadata associatedMetadata)
|
||||
protected RowEditBase(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata)
|
||||
{
|
||||
RowId = rowId;
|
||||
AssociatedResultSet = associatedResultSet;
|
||||
@@ -58,7 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <summary>
|
||||
/// The metadata for the table this edit is associated to
|
||||
/// </summary>
|
||||
public IEditTableMetadata AssociatedObjectMetadata { get; }
|
||||
public EditTableMetadata AssociatedObjectMetadata { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sort ID for a row edit. Ensures that when a collection of RowEditBase objects are
|
||||
@@ -162,7 +162,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
}
|
||||
|
||||
IList<DbCellValue> row = AssociatedResultSet.GetRow(RowId);
|
||||
foreach (EditColumnWrapper col in AssociatedObjectMetadata.KeyColumns)
|
||||
foreach (EditColumnMetadata col in AssociatedObjectMetadata.KeyColumns)
|
||||
{
|
||||
// Put together a clause for the value of the cell
|
||||
DbCellValue cellData = row[col.Ordinal];
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
|
||||
/// <param name="rowId">Internal ID of the row that will be updated with this object</param>
|
||||
/// <param name="associatedResultSet">Result set for the rows of the object to update</param>
|
||||
/// <param name="associatedMetadata">Metadata provider for the object to update</param>
|
||||
public RowUpdate(long rowId, ResultSet associatedResultSet, IEditTableMetadata associatedMetadata)
|
||||
public RowUpdate(long rowId, ResultSet associatedResultSet, EditTableMetadata associatedMetadata)
|
||||
: base(rowId, associatedResultSet, associatedMetadata)
|
||||
{
|
||||
cellUpdates = new ConcurrentDictionary<int, CellUpdate>();
|
||||
|
||||
Reference in New Issue
Block a user