Skip to content

Commit 1aa3b4f

Browse files
committed
feat: Spanner non-awaiting DDL
Adds a method to execute DDL on Spanner without having the client awaiting the long-running operation to finish. This makes it possible to for example start the creation of a secondary index using the client library, without having the library waiting for that operation to finish. Creating a secondary index on a table with a large amount of data can take a very long time (several days).
1 parent 8e77084 commit 1aa3b4f

File tree

3 files changed

+55
-9
lines changed

3 files changed

+55
-9
lines changed

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.IntegrationTests/AdminTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,17 +239,19 @@ AlbumTitle STRING(MAX),
239239
using (var connection = new SpannerConnection(builder.WithDatabase(dbName)))
240240
{
241241
var dropSingersCmd = connection.CreateDdlCommand("DROP TABLE Singers");
242-
var dropAlbumsCmd = connection.CreateDdlCommand("DROP TABLE Albums");
242+
var dropBothTablesCmd = connection.CreateDdlCommand("DROP TABLE Albums", "DROP TABLE Singers");
243243

244244
await Assert.ThrowsAsync<SpannerException>(() => dropSingersCmd.ExecuteNonQueryAsync());
245-
await dropAlbumsCmd.ExecuteNonQueryAsync();
246-
await dropSingersCmd.ExecuteNonQueryAsync();
245+
var operation = await dropBothTablesCmd.ExecuteDdlAsync();
246+
Assert.NotNull(operation?.Name);
247247
}
248248

249249
using (var connection = new SpannerConnection(builder))
250250
{
251251
var dropCommand = connection.CreateDdlCommand($"DROP DATABASE {dbName}");
252-
await dropCommand.ExecuteNonQueryAsync();
252+
var operation = await dropCommand.ExecuteDdlAsync();
253+
// DropDatabase does not return a long-running operation.
254+
Assert.Null(operation);
253255
}
254256
}
255257
}

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerCommand.ExecutableCommand.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Google.Cloud.Spanner.Admin.Database.V1;
1818
using Google.Cloud.Spanner.Common.V1;
1919
using Google.Cloud.Spanner.V1;
20+
using Google.LongRunning;
2021
using Google.Protobuf;
2122
using Google.Protobuf.WellKnownTypes;
2223
using Grpc.Core;
@@ -181,7 +182,7 @@ internal Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)
181182
switch (CommandTextBuilder.SpannerCommandType)
182183
{
183184
case SpannerCommandType.Ddl:
184-
return ExecuteDdlAsync(cancellationToken);
185+
return ExecuteDdlAsync(pollUntilCompleted: true, cancellationToken).ContinueWith(_ => 1, TaskContinuationOptions.ExecuteSynchronously);
185186
case SpannerCommandType.Delete:
186187
case SpannerCommandType.Insert:
187188
case SpannerCommandType.InsertOrUpdate:
@@ -243,12 +244,13 @@ private async Task<SpannerDataReader> ExecuteDmlReaderAsync(CommandBehavior beha
243244
return new SpannerDataReader(Connection.Logger, resultSet, Transaction?.ReadTimestamp, resourceToClose, ConversionOptions, enableGetSchemaTable, CommandTimeout);
244245
}
245246

246-
private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
247+
internal async Task<Operation> ExecuteDdlAsync(bool pollUntilCompleted, CancellationToken cancellationToken)
247248
{
248249
string commandText = CommandTextBuilder.CommandText;
249250
var builder = Connection.Builder;
250251
var channelOptions = new SpannerClientCreationOptions(builder);
251252
var credentials = await channelOptions.GetCredentialsAsync().ConfigureAwait(false);
253+
Operation operation = null;
252254

253255
// Create the builder separately from actually building, so we can note the channel that it created.
254256
// (This is fairly unpleasant, but we'll try to improve this in the next version of GAX.)
@@ -274,7 +276,11 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
274276
ProtoDescriptors = CommandTextBuilder.ProtobufDescriptors?.ToByteString() ?? ByteString.Empty,
275277
};
276278
var response = await databaseAdminClient.CreateDatabaseAsync(request).ConfigureAwait(false);
277-
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
279+
operation = response.RpcMessage;
280+
if (pollUntilCompleted)
281+
{
282+
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
283+
}
278284
if (response.IsFaulted)
279285
{
280286
throw SpannerException.FromOperationFailedException(response.Exception);
@@ -311,7 +317,11 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
311317
};
312318

313319
var response = await databaseAdminClient.UpdateDatabaseDdlAsync(request).ConfigureAwait(false);
314-
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
320+
operation = response.RpcMessage;
321+
if (pollUntilCompleted)
322+
{
323+
response = await response.PollUntilCompletedAsync().ConfigureAwait(false);
324+
}
315325
if (response.IsFaulted)
316326
{
317327
throw SpannerException.FromOperationFailedException(response.Exception);
@@ -328,7 +338,7 @@ private async Task<int> ExecuteDdlAsync(CancellationToken cancellationToken)
328338
channel?.Shutdown();
329339
}
330340

331-
return 0;
341+
return operation;
332342
}
333343

334344
private async Task<int> ExecuteMutationsAsync(CancellationToken cancellationToken)

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerCommand.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
using Google.Api.Gax;
1616
using Google.Cloud.Spanner.V1;
17+
using Google.LongRunning;
1718
using System;
1819
using System.Collections.Generic;
1920
using System.Data;
@@ -507,6 +508,39 @@ public long ExecutePartitionedUpdate() =>
507508
public Task<long> ExecutePartitionedUpdateAsync(CancellationToken cancellationToken = default) =>
508509
CreateExecutableCommand().ExecutePartitionedUpdateAsync(cancellationToken);
509510

511+
/// <summary>
512+
/// Executes this command as DDL. The command must contain one or more DDL statements;
513+
/// <see cref="SpannerConnection.CreateDdlCommand(string, string[])"/> for details.
514+
/// </summary>
515+
/// <param name="pollUntilCompleted">
516+
/// If true, the returned task will wait for the DDL operation to finish execution on Spanner.
517+
/// Otherwise, the returned task will finish as soon as the DDL operation has successfully been started on
518+
/// Spanner, and the caller can monitor the progress of the long-running operation.
519+
/// </param>
520+
/// <returns>
521+
/// A reference to the long-running operation that was started for the DDL statement(s).
522+
/// Note: The operation is null for DropDatabase commands.
523+
/// </returns>
524+
public Operation ExecuteDdl(bool pollUntilCompleted = false) =>
525+
Task.Run(() => ExecuteDdlAsync(pollUntilCompleted, _synchronousCancellationTokenSource.Token)).ResultWithUnwrappedExceptions();
526+
527+
/// <summary>
528+
/// Executes this command as DDL. The command must contain one or more DDL statements;
529+
/// <see cref="SpannerConnection.CreateDdlCommand(string, string[])"/> for details.
530+
/// </summary>
531+
/// <param name="pollUntilCompleted">
532+
/// If true, the returned task will wait for the DDL operation to finish execution on Spanner.
533+
/// Otherwise, the returned task will finish as soon as the DDL operation has successfully been started on
534+
/// Spanner, and the caller can monitor the progress of the long-running operation.
535+
/// </param>
536+
/// <param name="cancellationToken">An optional token for canceling the call.</param>
537+
/// <returns>
538+
/// A reference to the long-running operation that was started for the DDL statement(s).
539+
/// Note: The operation is null for DropDatabase commands.
540+
/// </returns>
541+
public Task<Operation> ExecuteDdlAsync(bool pollUntilCompleted = false, CancellationToken cancellationToken = default) =>
542+
CreateExecutableCommand().ExecuteDdlAsync(pollUntilCompleted, cancellationToken);
543+
510544
/// <summary>
511545
/// Creates an executable command that captures all the necessary information from this command.
512546
/// </summary>

0 commit comments

Comments
 (0)