Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scripting API implemented by the SqlScriptPublishModel #316

Merged
merged 34 commits into from Apr 24, 2017

Conversation

pensivebrian
Copy link
Member

@pensivebrian pensivebrian commented Apr 14, 2017

Update the ScriptingService to expose new scripting JSON-RPC APIs that use the SqlScriptPublishModel for script generation.

The SqlScriptPublishModel is the model behind the SSMS scripting wizard. To enable scripting for CLI tools, we've ported SqlScriptPublishModel to .NET Core. The SqlScriptPublishModel wraps the SMO scripting APIs for .sql script generation.

  • Added three new methods to the ScriptingService: ScriptingRequest, ScriptingListObjectsRequest, ScriptingCancelRequest.
  • Generating scripts are long running operations, so the ScriptingRequest and ScriptingListObjectsRequest kick off a long running scripting task and return immediately.
  • Long running scripting task reports progress and completion, and can be cancelled by a ScriptingCancelRequest request.
  • Bumped the SMO nuget package to 140.17049.0. This new version contains a signed SSMS_Rel build of SMO with the SqlScriptPublishModel.
  • For testing, adding the Northwind database schema

TODO (in later pull requests)

  • Integrate the new ScriptingService APIs with the ConnectionService
  • Integrate with the metadata support recently added

The SqlScriptPublishModel is the model behind the SSMS scripting wizard. It has been ported to .NET Core to enable scripting scenarios to SqlToolsService. This change adds the SqlScriptPublishModel nuget package to the checked in packages in the bin\nuget folder.
The scripting service uses the SqlScriptPublishModel to drive scripting operations.
Running against docker, you cannot user the server name from the connection string when composing a SMO Urn instance. The result is the SMO Urn won't resolve since the docker server name must be used, however you cannot use the docker server name in the connection string. The fix is to connect to the server with the passed connection string and resolve the server name used for SMO Urn.
@msftclas
Copy link

@pensivebrian,
Thanks for your contribution as a Microsoft full-time employee or intern. You do not need to sign a CLA.
Thanks,
Microsoft Pull Request Bot

@MrMeemus
Copy link
Contributor

@MrMeemus is added to the review. #Closed

/// <summary>
/// Looks up a localized string similar to TIME column values must be between 00:00:00.0000000 and 23:59:59.9999999.
/// </summary>
public static string EditDataTimeOver24Hrs {
Copy link
Contributor

@kevcunnane kevcunnane Apr 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditDataTimeOver24Hrs [](start = 29, length = 21)

The resx is in sync with this? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've sync'd the latest code from dev. It seems the checkin run srgen and include the latest sr.Designer.cs


In reply to: 111650561 [](ancestors = 111650561)


namespace Microsoft.SqlTools.ServiceLayer.Scripting
{
public static class ScriptingExtensionMethods
Copy link
Contributor

@kevcunnane kevcunnane Apr 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make internal or document any public classes / methods. Thanks! #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


In reply to: 111650593 [](ancestors = 111650593)

Copy link
Contributor

@kevcunnane kevcunnane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the test timeout on AppVeyor and the main service issue. All in all this is great, let's get this in once we fix these things.

After all tests run, if a static method "Cleanup" exists on a type, it's invoked so that class level test resources, such as a database, can be deleted.
@coveralls
Copy link

Coverage Status

Coverage decreased (-6.07%) to 75.002% when pulling 83afb81 on feature/scripterService into 88eb0f6 on dev.

Copy link
Member

@kburtram kburtram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this PR is having a big negative impact on code coverage. Please add additional test coverage to avoid this.

1) Use the existing pattern for kicking off async tasks for the scripting service operations.
2) Changed the SMO Urn format to not set the server name, so the Urn should resolve against the current server.
3) More logging
4) Use the xunit IClassFixture mechnism to manage the northwind database
5) Added docs
@coveralls
Copy link

Coverage Status

Coverage decreased (-3.8%) to 77.322% when pulling e492c32 on feature/scripterService into 88eb0f6 on dev.

Copy link
Contributor

@benrr101 benrr101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely some patterns I'd like to see addressed.

Also, I'd really really really like to see some unit tests for the service, and ideally more components. However, since a lot of this is SMO driven, it'll be harder to mock it up. Doesn't mean it's impossible..... at the very least please create an issue to address this going forward.

/// <summary>
/// Event sent to indicate that a scripting operation has been canceled.
/// </summary>
public class ScriptingCancelEvent
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In previous API requests (edit/initialize in particular) we have a single event that is raised when the initialization is completed (edit/initializeSessionReady). The event has parameters to indicate success or failure. This also makes it simpler on the client side to implement b/c there is only one listener for the edit/initializeSessionReady event. In this scenario, you'll need to create 3 listeners (complete, cancel, and error) that will need to be shut down when any one of them receives an event.
Since you've got a different payload to include with each kind of event, I can see why using separate events would be beneficial, but it is slightly different than the APIs already developed. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to combine completed and cancel if that's the pattern used by services.

/// <summary>
/// Gets or sets the operation id of the scripting operation to cancel.
/// </summary>
public string OperationId { get; set; }
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this should inherit from ScriptingEventParams? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, nice catch.

return objectName;
}

public override int GetHashCode()
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping this is the auto-implemented GetHashCode from ReSharper 😃 #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I don't use ReSharper :)

/// <summary>
/// Generate ANSI padding statements
/// </summary>
public string ScriptAnsiPadding { get; set; }
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.NET very successfully handles Nullables. I would strongly advise switching to that since it provides much better type safety. If you're really not into changing these to Nullable, I would even more strongly advise adding a comment to indicate what the expected values are (eg, I thought ScriptAnsiPadding was a value for what the ANSI padding character should be, not whether or not it should generate padding statements). #Resolved

/// <summary>
/// Convert user-defined data types to base types.
/// </summary>
public string ConvertUDDTToBaseType { get; set; }
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per MSDN standards (which I don't have a link to, atm) acronyms in variable names should only have the first character capitalized if >2 characters #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the same names used by SMO. I'm my mind, this makes it easier to reason over all of options. I'm going to leave as-is for now unless folks feel strongly.


In reply to: 112277263 [](ancestors = 112277263)

}
catch (Exception e)
{
throw new ArgumentException("Error parsing ConnectionString property", e);
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed this, but make sure that all these exception messages are being added to strings.sr. They will need to be localized if they will be sent over JSON RPC #Resolved

{
try
{
this.ActiveOperations[operation.OperationId] = operation;
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be using the TryAdd method, otherwise it's possible to orphan an in progress operation #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new operation is instantiated per request, and each has a unique operation id so this should never happen. I added debug assert to make this more explicit.

finally
{
ScriptingOperation temp;
this.ActiveOperations.TryRemove(operation.OperationId, out temp);
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this just remove the operation every time you start it up, regardless of if it fails? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

/// </summary>
private void RunScriptTask(ScriptingScriptOperation operation)
{
Task.Run(async () =>
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure the benefit of wrapping this in a Task.Run... If all the operation.Executes don't await anything, then there's no need to make .Execute async. Then you don't need to wrap the startup operation in a Task. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the await is not needed. I removed the awaits and made Execute synchronous.

/// </summary>
private void RunScriptTask(ScriptingScriptOperation operation)
{
Task.Run(async () =>
Copy link
Contributor

@benrr101 benrr101 Apr 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'm also curious since this pattern is seemingly reused several times, why isn't this method used more than once? Or more concisely, what makes this private method different than the other implementations of the same pattern. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I refactored to single method reused by the request handlers.

@coveralls
Copy link

Coverage Status

Coverage increased (+12.9%) to 81.418% when pulling 327c659 on feature/scripterService into 84ec20d on dev.

1) Keep the RequestContext isolated to the service
2) Combine completion/error/cancel events
3) Misc code review fixes
1) Cancellation logic moved into base class
2) Refactored sending events from the scripting service
3) Make all tests pass
@pensivebrian pensivebrian dismissed stale reviews from kburtram and benrr101 April 21, 2017 16:35

My tests now run, and coveage was above back up to 80% before the latest drop

namespace Microsoft.SqlTools.ServiceLayer.Scripting
{
/// <summary>
/// Implementes the matchin logic to filter scripting objects based on
Copy link
Contributor

@kevcunnane kevcunnane Apr 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor typos: Implements the matching logic #Resolved

Copy link
Contributor

@kevcunnane kevcunnane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@coveralls
Copy link

Coverage Status

Coverage decreased (-12.9%) to 68.776% when pulling 60b4afd on feature/scripterService into 06eddd5 on dev.

@coveralls
Copy link

Coverage Status

Coverage increased (+8.6%) to 76.134% when pulling aeffd07 on feature/scripterService into e65699e on dev.

@pensivebrian pensivebrian merged commit 4aac4a4 into dev Apr 24, 2017
@benrr101 benrr101 deleted the feature/scripterService branch April 25, 2017 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants