Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -239,17 +239,19 @@ AlbumTitle STRING(MAX),
using (var connection = new SpannerConnection(builder.WithDatabase(dbName)))
{
var dropSingersCmd = connection.CreateDdlCommand("DROP TABLE Singers");
var dropAlbumsCmd = connection.CreateDdlCommand("DROP TABLE Albums");
var dropBothTablesCmd = connection.CreateDdlCommand("DROP TABLE Albums", "DROP TABLE Singers");

await Assert.ThrowsAsync<SpannerException>(() => dropSingersCmd.ExecuteNonQueryAsync());
await dropAlbumsCmd.ExecuteNonQueryAsync();
await dropSingersCmd.ExecuteNonQueryAsync();
var operation = await dropBothTablesCmd.ExecuteDdlAsync();
Assert.NotNull(operation?.Name);
}

using (var connection = new SpannerConnection(builder))
{
var dropCommand = connection.CreateDdlCommand($"DROP DATABASE {dbName}");
await dropCommand.ExecuteNonQueryAsync();
var operation = await dropCommand.ExecuteDdlAsync();
// DropDatabase does not return a long-running operation.
Assert.Null(operation);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,11 @@
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/LINE_FEED_AT_FILE_END/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_EMBEDDED_STATEMENT_ON_SAME_LINE/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
Expand Down Expand Up @@ -290,7 +293,7 @@
&lt;/Patterns&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
<s:Boolean x:Key="/Default/CodeStyle/EncapsulateField/UpdateExternalUsagesOnly/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright $CREATED_YEAR$ Google Inc. All Rights Reserved.&#xD;
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright ${File.CreatedYear} Google Inc. All Rights Reserved.&#xD;
&#xD;
Licensed under the Apache License, Version 2.0 (the "License");&#xD;
you may not use this file except in compliance with the License.&#xD;
Expand All @@ -314,6 +317,7 @@ limitations under the License.&#xD;
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PS/@EntryIndexedValue">PS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="s_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="s_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FVARIABLE/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
Expand Down Expand Up @@ -365,10 +369,18 @@ limitations under the License.&#xD;
<s:Boolean x:Key="/Default/Environment/Feedback/ShouldPrompt/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/Environment/Hierarchy/GeneratedFilesCacheKey/Timestamp/@EntryValue">6</s:Int64>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\przybjw\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_c19b1865\SolutionCaches</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EApplication_002EExceptionReport_002EUserLoginInformationMigrateSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002ECodeCleanup_002EFileHeader_002EFileHeaderSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002EMemberReordering_002EMigrations_002ECSharpFileLayoutPatternRemoveIsAttributeUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsParsFormattingSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ESettingsUpgrade_002EJsWrapperSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EJavaScript_002ECodeStyle_002ETypeScript_002ESettingsUpgrade_002ETsModuleAliasesSettingsUpgrader/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -381,7 +393,7 @@ limitations under the License.&#xD;
<s:Boolean x:Key="/Default/Housekeeping/FeatureSuggestion/FeatureSuggestionManager/DisabledSuggesters/=ReSharperBuildSuggester/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/FeatureSuggestion/FeatureSuggestionManager/DisabledSuggesters/=SwitchToGoToActionSuggester/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/FeatureSuggestion/FeatureSuggestionManager/DisabledSuggesters/=TabNavigationExplainer/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Housekeeping/FeedbackReport/SelectedUserIdentificator/@EntryValue">anonymous</s:String>

<s:Boolean x:Key="/Default/Housekeeping/GlobalSettingsUpgraded/IsUpgraded/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/IntellisenseHousekeeping/HintUsed/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/Housekeeping/Layout/DialogWindows/OptionsDialog/Position/@EntryValue">[1703,-56](1314,749)</s:String>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using Google.Cloud.Spanner.V1;
using Google.LongRunning;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
Expand Down Expand Up @@ -235,10 +236,17 @@ private async Task<SpannerDataReader> ExecuteDmlReaderAsync(CommandBehavior beha
}

private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
{
await ExecuteDdlAsync(pollUntilCompleted: true, cancellationToken).ConfigureAwait(false);
return 0;
}

internal async Task<Operation> ExecuteDdlAsync(bool pollUntilCompleted, CancellationToken cancellationToken)
{
string commandText = CommandTextBuilder.CommandText;
var builder = Connection.Builder;
var connectionOptions = new SpannerClientCreationOptions(builder);
Operation operation = null;

// Create the builder separately from actually building, so we can note the channel that it created.
// (This is fairly unpleasant, but we'll try to improve this in the next version of GAX.)
Expand All @@ -258,7 +266,11 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
ProtoDescriptors = CommandTextBuilder.ProtobufDescriptors?.ToByteString() ?? ByteString.Empty,
};
var response = await databaseAdminClient.CreateDatabaseAsync(request).ConfigureAwait(false);
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
operation = response.RpcMessage;
if (pollUntilCompleted)
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding this if in two places, let's have this method return the un-polled operation and the wrapping ones poll until completion if they need to.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is that really possible in this case, considering the fact that the two methods return two different operation types:

  1. Operation<Database, CreateDatabaseMetadata>
  2. Operation<Empty, UpdateDatabaseDdlMetadata>

(Type non-generic Operation class cannot be used for polling)

Copy link
Contributor

Choose a reason for hiding this comment

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

If the goal is to remove duplication might I instead suggest a a generic local function to centralize the polling and error-handling logic (at the bottom of this method):

async Task<Operation> HandleLro<TResp, TMeta>(Operation<TResp, TMeta> operation)
{
    if (pollUntilCompleted)
    {
        operation = await operation.PollUntilCompletedAsync().ConfigureAwait(false);
    }
    if (operation.IsFaulted)
    {
        throw SpannerException.FromOperationFailedException(operation.Exception);
    }
    return operation;
}

If we go the other way and use the returned Operation to poll later, the calling method will add extra overhead because it needs to establish a new channel since this method shuts down the one it creates in the finally block.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes +1 to Robert's comment, I hadn't noticed the channel being closed.

{
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
}
if (response.IsFaulted)
{
throw SpannerException.FromOperationFailedException(response.Exception);
Expand Down Expand Up @@ -295,7 +307,11 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
};

var response = await databaseAdminClient.UpdateDatabaseDdlAsync(request).ConfigureAwait(false);
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
operation = response.RpcMessage;
if (pollUntilCompleted)
{
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
}
if (response.IsFaulted)
{
throw SpannerException.FromOperationFailedException(response.Exception);
Expand All @@ -312,7 +328,7 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
channel?.Shutdown();
}

return 0;
return operation;
}

private async Task<int> ExecuteMutationsAsync(CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

using Google.Api.Gax;
using Google.Cloud.Spanner.V1;
using Google.LongRunning;
using System;
using System.Collections.Generic;
using System.Data;
Expand Down Expand Up @@ -513,6 +514,39 @@ public long ExecutePartitionedUpdate() =>
public Task<long> ExecutePartitionedUpdateAsync(CancellationToken cancellationToken = default) =>
CreateExecutableCommand().ExecutePartitionedUpdateAsync(cancellationToken);

/// <summary>
/// Executes this command as DDL. The command must contain one or more DDL statements;
/// <see cref="SpannerConnection.CreateDdlCommand(string, string[])"/> for details.
/// </summary>
/// <param name="pollUntilCompleted">
/// If true, the returned task will wait for the DDL operation to finish execution on Spanner.
/// Otherwise, the returned task will finish as soon as the DDL operation has successfully been started on
/// Spanner, and the caller can monitor the progress of the long-running operation.
/// </param>
/// <returns>
/// A reference to the long-running operation that was started for the DDL statement(s).
/// Note: The operation is null for DropDatabase commands.
/// </returns>
public Operation ExecuteDdl(bool pollUntilCompleted = false) =>
Task.Run(() => ExecuteDdlAsync(pollUntilCompleted, _synchronousCancellationTokenSource.Token)).ResultWithUnwrappedExceptions();

/// <summary>
/// Executes this command as DDL. The command must contain one or more DDL statements;
/// <see cref="SpannerConnection.CreateDdlCommand(string, string[])"/> for details.
/// </summary>
/// <param name="pollUntilCompleted">
/// If true, the returned task will wait for the DDL operation to finish execution on Spanner.
/// Otherwise, the returned task will finish as soon as the DDL operation has successfully been started on
/// Spanner, and the caller can monitor the progress of the long-running operation.
/// </param>
/// <param name="cancellationToken">An optional token for canceling the call.</param>
/// <returns>
/// A reference to the long-running operation that was started for the DDL statement(s).
/// Note: The operation is null for DropDatabase commands.
/// </returns>
public Task<Operation> ExecuteDdlAsync(bool pollUntilCompleted = false, CancellationToken cancellationToken = default) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

We usually don't expose the underlying types in the ADO.NET layer. Is it really needed to return an operation? And in that case what part of the Operation is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced with Task<string> StartDdlAsync

CreateExecutableCommand().ExecuteDdlAsync(pollUntilCompleted, cancellationToken);

/// <summary>
/// Creates an executable command that captures all the necessary information from this command.
/// </summary>
Expand Down