Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
31 changes: 31 additions & 0 deletions src/Service/Models/DbOperationResultRow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;

namespace Azure.DataApiBuilder.Service.Models
{
/// <summary>
/// Represents a single row read from DbDataReader.
/// </summary>
public class DbOperationResultRow
{
public DbOperationResultRow(
Dictionary<string, object?> columns,
Dictionary<string, object> resultProperties)
{
this.Columns = columns;
this.ResultProperties = resultProperties;
}

/// <summary>
/// Represents a result set row in <c>ColumnName: Value</c> format, empty if no row was found.
/// </summary>
public Dictionary<string, object?> Columns { get; private set; }

/// <summary>
/// Represents DbDataReader properties such as RecordsAffected and HasRows.
/// </summary>
public Dictionary<string, object> ResultProperties { get; private set; }
}
}
22 changes: 10 additions & 12 deletions src/Service/Resolvers/IQueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Data.Common;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Azure.DataApiBuilder.Service.Models;
using Microsoft.AspNetCore.Http;

namespace Azure.DataApiBuilder.Service.Resolvers
Expand All @@ -30,7 +31,7 @@ public interface IQueryExecutor
public Task<TResult?> ExecuteQueryAsync<TResult>(
string sqltext,
IDictionary<string, object?> parameters,
Func<DbDataReader, List<string>?, Task<TResult?>>? dataReaderHandler,
Func<DbDataReader, List<string>?, Task<TResult>>? dataReaderHandler,
HttpContext? httpContext = null,
List<string>? args = null);

Expand All @@ -41,7 +42,7 @@ public interface IQueryExecutor
/// <param name="dbDataReader">A DbDataReader.</param>
/// <param name="args">List of string arguments if any.</param>
/// <returns>A JsonArray with each element corresponding to the row (ColumnName : columnValue) in the dbDataReader.</returns>
public Task<JsonArray?> GetJsonArrayAsync(
public Task<JsonArray> GetJsonArrayAsync(
DbDataReader dbDataReader,
List<string>? args = null);

Expand All @@ -62,10 +63,8 @@ public interface IQueryExecutor
/// </summary>
/// <param name="dbDataReader">A DbDataReader</param>
/// <param name="args">List of columns to extract. Extracts all if unspecified.</param>
/// <returns>A tuple of 2 dictionaries:
/// 1. A dictionary representing the row in <c>ColumnName: Value</c> format, null if no row was found
/// 2. A dictionary of properties of the Db Data Reader like RecordsAffected, HasRows.</returns>
public Task<Tuple<Dictionary<string, object?>?, Dictionary<string, object>>?> ExtractRowFromDbDataReader(
/// <returns>Single row read from DbDataReader.</returns>
public Task<DbOperationResultRow> ExtractRowFromDbDataReader(
DbDataReader dbDataReader,
List<string>? args = null);

Expand All @@ -76,11 +75,10 @@ public interface IQueryExecutor
/// </summary>
/// <param name="dbDataReader">A DbDataReader.</param>
/// <param name="args">The arguments to this handler - args[0] = primary key in pretty format, args[1] = entity name.</param>
/// <returns>A tuple of 2 dictionaries:
/// 1. A dictionary representing the row in <c>ColumnName: Value</c> format.
/// 2. A dictionary of properties of the DbDataReader like RecordsAffected, HasRows.
/// If the first result set is being returned, has the property "IsFirstResultSet" set to true in this dictionary.</returns>
public Task<Tuple<Dictionary<string, object?>?, Dictionary<string, object>>?> GetMultipleResultSetsIfAnyAsync(
/// <returns>Single row read from DbDataReader.
/// If the first result set is being returned, DbOperationResultRow.ResultProperties dictionary has
/// the property "IsFirstResultSet" set to true.</returns>
public Task<DbOperationResultRow> GetMultipleResultSetsIfAnyAsync(
DbDataReader dbDataReader,
List<string>? args = null);

Expand All @@ -90,7 +88,7 @@ public interface IQueryExecutor
/// <param name="dbDataReader">A DbDataReader.</param>
/// <param name="args">List of string arguments if any.</param>
/// <returns>A dictionary of properties of the DbDataReader like RecordsAffected, HasRows.</returns>
public Task<Dictionary<string, object>?> GetResultProperties(
public Task<Dictionary<string, object>> GetResultProperties(
DbDataReader dbDataReader,
List<string>? args = null);

Expand Down
62 changes: 29 additions & 33 deletions src/Service/Resolvers/QueryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Threading.Tasks;
using Azure.DataApiBuilder.Service.Configurations;
using Azure.DataApiBuilder.Service.Exceptions;
using Azure.DataApiBuilder.Service.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Polly;
Expand Down Expand Up @@ -62,7 +63,7 @@ public QueryExecutor(DbExceptionParser dbExceptionParser,
public virtual async Task<TResult?> ExecuteQueryAsync<TResult>(
string sqltext,
IDictionary<string, object?> parameters,
Func<DbDataReader, List<string>?, Task<TResult?>>? dataReaderHandler,
Func<DbDataReader, List<string>?, Task<TResult>>? dataReaderHandler,
HttpContext? httpContext = null,
List<string>? args = null)
{
Expand Down Expand Up @@ -134,7 +135,7 @@ await ExecuteQueryAgainstDbAsync(conn,
TConnection conn,
string sqltext,
IDictionary<string, object?> parameters,
Func<DbDataReader, List<string>?, Task<TResult?>>? dataReaderHandler,
Func<DbDataReader, List<string>?, Task<TResult>>? dataReaderHandler,
HttpContext? httpContext,
List<string>? args = null)
{
Expand Down Expand Up @@ -208,20 +209,20 @@ public async Task<bool> ReadAsync(DbDataReader reader)
}

/// <inheritdoc />
public async Task<Tuple<Dictionary<string, object?>?, Dictionary<string, object>>?>
public async Task<DbOperationResultRow>
ExtractRowFromDbDataReader(DbDataReader dbDataReader, List<string>? args = null)
{
Dictionary<string, object?> row = new();

Dictionary<string, object> propertiesOfResult = GetResultProperties(dbDataReader).Result ?? new();
DbOperationResultRow dbOperationResultRow = new(
columns: new(),
resultProperties: GetResultProperties(dbDataReader).Result ?? new());

if (await ReadAsync(dbDataReader))
{
if (dbDataReader.HasRows)
{
DataTable? schemaTable = dbDataReader.GetSchemaTable();

if (schemaTable != null)
if (schemaTable is not null)
{
foreach (DataRow schemaRow in schemaTable.Rows)
{
Expand All @@ -235,43 +236,37 @@ public async Task<bool> ReadAsync(DbDataReader reader)
int colIndex = dbDataReader.GetOrdinal(columnName);
if (!dbDataReader.IsDBNull(colIndex))
{
row.Add(columnName, dbDataReader[columnName]);
dbOperationResultRow.Columns.Add(columnName, dbDataReader[columnName]);
}
else
{
row.Add(columnName, value: null);
dbOperationResultRow.Columns.Add(columnName, value: null);
}
}
}
}
}

// no row was read
if (row.Count == 0)
{
return new Tuple<Dictionary<string, object?>?, Dictionary<string, object>>(null, propertiesOfResult);
}

return new Tuple<Dictionary<string, object?>?, Dictionary<string, object>>(row, propertiesOfResult);
return dbOperationResultRow;
}

/// <inheritdoc />
/// <Note>This function is a DbDataReader handler of type Func<DbDataReader, List<string>?, Task<TResult?>>
/// The parameter args is not used but is added to conform to the signature of the DbDataReader handler
/// function argument of ExecuteQueryAsync.</Note>
public async Task<JsonArray?> GetJsonArrayAsync(
public async Task<JsonArray> GetJsonArrayAsync(
DbDataReader dbDataReader,
List<string>? args = null)
{
Tuple<Dictionary<string, object?>?, Dictionary<string, object>>? resultRowAndProperties;
DbOperationResultRow dbOperationResultRow = await ExtractRowFromDbDataReader(dbDataReader);
JsonArray resultArray = new();

while ((resultRowAndProperties = await ExtractRowFromDbDataReader(dbDataReader)) is not null &&
resultRowAndProperties.Item1 is not null)
while (dbOperationResultRow.Columns.Count > 0)
{
JsonElement result =
JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(resultRowAndProperties.Item1));
JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(dbOperationResultRow.Columns));
resultArray.Add(result);
dbOperationResultRow = await ExtractRowFromDbDataReader(dbDataReader);
}

return resultArray;
Expand Down Expand Up @@ -306,21 +301,20 @@ public async Task<bool> ReadAsync(DbDataReader reader)
/// <inheritdoc />
/// <Note>This function is a DbDataReader handler of type
/// Func<DbDataReader, List<string>?, Task<TResult?>></Note>
public async Task<Tuple<Dictionary<string, object?>?, Dictionary<string, object>>?> GetMultipleResultSetsIfAnyAsync(
public async Task<DbOperationResultRow> GetMultipleResultSetsIfAnyAsync(
DbDataReader dbDataReader, List<string>? args = null)
{
Tuple<Dictionary<string, object?>?, Dictionary<string, object>>? resultRecordWithProperties
DbOperationResultRow dbOperationResultRow
= await ExtractRowFromDbDataReader(dbDataReader);

/// Processes a second result set from DbDataReader if it exists.
/// In MsSQL upsert:
/// result set #1: result of the UPDATE operation.
/// result set #2: result of the INSERT operation.
if (resultRecordWithProperties is not null && resultRecordWithProperties.Item1 is not null)
if (dbOperationResultRow.Columns.Count > 0)
{
resultRecordWithProperties.Item2.Add(SqlMutationEngine.IS_FIRST_RESULT_SET, true);
return new Tuple<Dictionary<string, object?>?, Dictionary<string, object>>
(resultRecordWithProperties.Item1, resultRecordWithProperties.Item2);
dbOperationResultRow.ResultProperties.Add(SqlMutationEngine.IS_FIRST_RESULT_SET, true);
return dbOperationResultRow;
}
else if (await dbDataReader.NextResultAsync())
{
Expand All @@ -345,20 +339,22 @@ public async Task<bool> ReadAsync(DbDataReader reader)
}
}

return null;
return dbOperationResultRow;
}

/// <inheritdoc />
/// <Note>This function is a DbDataReader handler of type
/// Func<DbDataReader, List<string>?, Task<TResult?>></Note>
public Task<Dictionary<string, object>?> GetResultProperties(
public Task<Dictionary<string, object>> GetResultProperties(
DbDataReader dbDataReader,
List<string>? columnNames = null)
{
Dictionary<string, object>? propertiesOfResult = new();
propertiesOfResult.Add(nameof(dbDataReader.RecordsAffected), dbDataReader.RecordsAffected);
propertiesOfResult.Add(nameof(dbDataReader.HasRows), dbDataReader.HasRows);
return Task.FromResult((Dictionary<string, object>?)propertiesOfResult);
Dictionary<string, object> resultProperties = new()
{
{ nameof(dbDataReader.RecordsAffected), dbDataReader.RecordsAffected },
{ nameof(dbDataReader.HasRows), dbDataReader.HasRows }
};
return Task.FromResult(resultProperties);
}

private async Task<string> GetJsonStringFromDbReader(DbDataReader dbDataReader)
Expand Down
Loading