/// <inheritdoc /> public override Task<bool> ReadAsync(CancellationToken cancellationToken) { return ExecuteHelper.WithErrorTranslationAndProfiling( async () => { await PopulateMetadataAsync(cancellationToken).ConfigureAwait(false); _innerList.Clear(); //read # values == # fields. var first = await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false); if (first == null) { return false; } _innerList.Add(first); //we expect to get full rows... for (var i = 1; i < _metadata.RowType.Fields.Count; i++) { _innerList.Add(await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false)); } return true; }, "SpannerDataReader.Read"); }
/// <summary> /// Reads the next row of values from Cloud Spanner. /// Important: Cloud Spanner supports limited cancellation of this task. /// </summary> /// <param name="cancellationToken">A cancellation token to cancel the read. Cloud Spanner currently /// supports limited cancellation while advancing the read to the next row.</param> /// <returns>True if another row was read.</returns> public override Task <bool> ReadAsync(CancellationToken cancellationToken) { return(ExecuteHelper.WithErrorTranslationAndProfiling( async() => { if (_metadata == null) { await PopulateMetadataAsync(cancellationToken).ConfigureAwait(false); } _rowValid = false; _innerList.Clear(); var first = await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false); if (first == null) { return false; } _innerList.Add(first); // We expect to get full rows... for (var i = 1; i < _metadata.RowType.Fields.Count; i++) { var value = await _resultSet.NextAsync(cancellationToken).ConfigureAwait(false); GaxPreconditions.CheckState(value != null, "Incomplete row returned by Spanner server"); _innerList.Add(value); } _rowValid = true; return true; }, "SpannerDataReader.Read", Logger)); }
/// <summary> /// Reads the next row of values from Cloud Spanner. /// Important: Cloud Spanner supports limited cancellation of this task. /// </summary> /// <param name="cancellationToken">A cancellation token to cancel the read. Cloud Spanner currently /// supports limited cancellation while advancing the read to the next row.</param> /// <returns>True if another row was read.</returns> public override Task <bool> ReadAsync(CancellationToken cancellationToken) => ExecuteHelper.WithErrorTranslationAndProfiling(async() => { using (var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(_readTimeoutSeconds))) { var timeoutToken = timeoutCts.Token; using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken)) { try { var effectiveToken = linkedCts.Token; if (_metadata == null) { await PopulateMetadataAsync(effectiveToken).ConfigureAwait(false); } _rowValid = false; _innerList.Clear(); var first = await _resultSet.NextAsync(effectiveToken).ConfigureAwait(false); if (first == null) { // If this is the first thing we've tried to read, then we know there are no rows. if (_hasRows == null) { _hasRows = false; } return(false); } _innerList.Add(first); // We expect to get full rows... for (var i = 1; i < _metadata.RowType.Fields.Count; i++) { var value = await _resultSet.NextAsync(effectiveToken).ConfigureAwait(false); GaxPreconditions.CheckState(value != null, "Incomplete row returned by Spanner server"); _innerList.Add(value); } _rowValid = true; _hasRows = true; return(true); } // Translate timeouts from our own cancellation token into a Spanner exception. // This mimics the behavior of SqlDataReader, which throws a SqlException on timeout. // It's *possible* that the operation was cancelled due to the user-provided cancellation token, // and that it just happens that the timeout has been fired as well... but there's a race // condition anyway in that case, so it's probably reasonable to take the simple path. catch (OperationCanceledException) when(timeoutToken.IsCancellationRequested) { throw new SpannerException(ErrorCode.DeadlineExceeded, "Read operation timed out"); } } } }, "SpannerDataReader.Read", Logger);