зеркало из https://github.com/microsoft/Power-Fx.git
Tabular connector refactoring (#2404)
no swagger no v2 add ConnectorDataSource get Dataset Metadata
This commit is contained in:
Родитель
ebe58cf32a
Коммит
f1afe8c523
|
@ -1,160 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
// Implements CDP protocol for Tabular connectors
|
||||
public class CdpTabularService : TabularService
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string DisplayName { get; private set; }
|
||||
|
||||
public string DatasetName { get; protected set; }
|
||||
|
||||
public string TableName { get; protected set; }
|
||||
|
||||
public override bool IsDelegable => TableCapabilities?.IsDelegable ?? false;
|
||||
|
||||
internal ServiceCapabilities TableCapabilities { get; private set; }
|
||||
|
||||
private string _uriPrefix;
|
||||
private bool _v2;
|
||||
|
||||
public CdpTabularService(string dataset, string table)
|
||||
{
|
||||
DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset));
|
||||
TableName = table ?? throw new ArgumentNullException(nameof(table));
|
||||
}
|
||||
|
||||
protected CdpTabularService()
|
||||
{
|
||||
}
|
||||
|
||||
//// TABLE METADATA SERVICE
|
||||
// GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01
|
||||
public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, bool useV2, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
logger?.LogInformation($"Entering in {nameof(CdpTabularService)} {nameof(InitAsync)} for {DatasetName}, {TableName}");
|
||||
|
||||
if (IsInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("TabularService already initialized");
|
||||
}
|
||||
|
||||
_v2 = useV2;
|
||||
_uriPrefix = uriPrefix;
|
||||
|
||||
string uri = (_uriPrefix ?? string.Empty) + (_v2 ? "/v2" : string.Empty) + $"/$metadata.json/datasets/{DoubleEncode(DatasetName, "dataset")}/tables/{DoubleEncode(TableName, "table")}?api-version=2015-09-01";
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string text = response?.Content == null ? string.Empty : await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
int statusCode = (int)response.StatusCode;
|
||||
|
||||
string reasonPhrase = string.IsNullOrEmpty(response.ReasonPhrase) ? string.Empty : $" ({response.ReasonPhrase})";
|
||||
logger?.LogInformation($"Exiting {nameof(CdpTabularService)} {nameof(InitAsync)} for {DatasetName}, {TableName} with Http Status {statusCode}{reasonPhrase}{(statusCode < 300 ? string.Empty : text)}");
|
||||
|
||||
if (statusCode < 300)
|
||||
{
|
||||
SetTableType(GetSchema(text));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogException(ex, $"Exception in {nameof(CdpTabularService)} {nameof(InitAsync)} for {DatasetName}, {TableName}, v2: {_v2}, {ConnectorHelperFunctions.LogException(ex)}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string DoubleEncode(string param, string paramName)
|
||||
{
|
||||
// we force double encoding here (in swagger, we have "x-ms-url-encoding": "double")
|
||||
return HttpUtility.UrlEncode(HttpUtility.UrlEncode(param));
|
||||
}
|
||||
|
||||
internal RecordType GetSchema(string text)
|
||||
{
|
||||
ConnectorType connectorType = ConnectorFunction.GetConnectorTypeAndTableCapabilities("Schema/Items", FormulaValue.New(text), ConnectorCompatibility.SwaggerCompatibility, out string name, out string displayName, out ServiceCapabilities tableCapabilities);
|
||||
Name = name;
|
||||
DisplayName = displayName;
|
||||
TableCapabilities = tableCapabilities;
|
||||
|
||||
// Note that connectorType contains columns' capabilities but not the FormulaType (as of current developement)
|
||||
return connectorType?.FormulaType as RecordType;
|
||||
}
|
||||
|
||||
// TABLE DATA SERVICE - CREATE
|
||||
// POST: /datasets/{datasetName}/tables/{tableName}/items?api-version=2015-09-01
|
||||
|
||||
// TABLE DATA SERVICE - READ
|
||||
// GET AN ITEM - GET: /datasets/{datasetName}/tables/{tableName}/items/{id}?api-version=2015-09-01
|
||||
|
||||
// LIST ITEMS - GET: /datasets/{datasetName}/tables/{tableName}/items?$filter=’CreatedBy’ eq ‘john.doe’&$top=50&$orderby=’Priority’ asc, ’CreationDate’ desc
|
||||
protected override async Task<IReadOnlyCollection<DValue<RecordValue>>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters odataParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ConnectorLogger executionLogger = serviceProvider?.GetService<ConnectorLogger>();
|
||||
HttpClient httpClient = serviceProvider?.GetService<HttpClient>() ?? throw new InvalidOperationException("HttpClient is required on IServiceProvider");
|
||||
|
||||
try
|
||||
{
|
||||
executionLogger?.LogInformation($"Entering in {nameof(CdpTabularService)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName}");
|
||||
|
||||
Uri uri = new Uri((_uriPrefix ?? string.Empty) + (_v2 ? "/v2" : string.Empty) + $"/datasets/{HttpUtility.UrlEncode(DatasetName)}/tables/{HttpUtility.UrlEncode(TableName)}/items?api-version=2015-09-01", UriKind.Relative);
|
||||
|
||||
if (odataParameters != null)
|
||||
{
|
||||
uri = odataParameters.GetUri(uri);
|
||||
}
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string text = response?.Content == null ? string.Empty : await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
int statusCode = (int)response.StatusCode;
|
||||
|
||||
string reasonPhrase = string.IsNullOrEmpty(response.ReasonPhrase) ? string.Empty : $" ({response.ReasonPhrase})";
|
||||
executionLogger?.LogInformation($"Exiting {nameof(CdpTabularService)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName} with Http Status {statusCode}{reasonPhrase}{(statusCode < 300 ? string.Empty : text)}");
|
||||
|
||||
return statusCode < 300 ? GetResult(text) : Array.Empty<DValue<RecordValue>>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
executionLogger?.LogException(ex, $"Exception in {nameof(CdpTabularService)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName}, v2: {_v2}, {ConnectorHelperFunctions.LogException(ex)}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected IReadOnlyCollection<DValue<RecordValue>> GetResult(string text)
|
||||
{
|
||||
// $$$ Is this always this type?
|
||||
RecordValue rv = FormulaValueJSON.FromJson(text, RecordType.Empty().Add("value", TableType)) as RecordValue;
|
||||
TableValue tv = rv.Fields.FirstOrDefault(field => field.Name == "value").Value as TableValue;
|
||||
|
||||
// The call we make contains more fields and we want to remove them here ('@odata.etag')
|
||||
return new InMemoryTableValue(IRContext.NotInSource(TableType), tv.Rows).Rows.ToArray();
|
||||
}
|
||||
|
||||
// TABLE DATA SERVICE - UPDATE
|
||||
// PATCH: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
|
||||
// TABLE DATA SERVICE - DELETE
|
||||
// DELETE: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
public class ConnectorDataSource : ConnectorServiceBase
|
||||
{
|
||||
public string DatasetName { get; protected set; }
|
||||
|
||||
private string _uriPrefix;
|
||||
|
||||
internal DatasetMetadata DatasetMetadata { get; private set; }
|
||||
|
||||
public ConnectorDataSource(string dataset)
|
||||
{
|
||||
DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset));
|
||||
}
|
||||
|
||||
internal async Task GetDatasetsMetadataAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
_uriPrefix = uriPrefix;
|
||||
|
||||
string uri = (_uriPrefix ?? string.Empty)
|
||||
+ (uriPrefix.Contains("/sql/") ? "/v2" : string.Empty)
|
||||
+ $"/$metadata.json/datasets";
|
||||
|
||||
DatasetMetadata = await GetObject<DatasetMetadata>(httpClient, "Get datasets metadata", uri, cancellationToken, logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ConnectorTable>> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
if (DatasetMetadata == null)
|
||||
{
|
||||
await GetDatasetsMetadataAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
string uri = (_uriPrefix ?? string.Empty)
|
||||
+ (uriPrefix.Contains("/sql/") ? "/v2" : string.Empty)
|
||||
+ $"/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}"
|
||||
+ (uriPrefix.Contains("/sharepointonline/") ? "/alltables" : "/tables");
|
||||
|
||||
GetTables tables = await GetObject<GetTables>(httpClient, "Get tables", uri, cancellationToken, logger).ConfigureAwait(false);
|
||||
return tables?.Value?.Select(rt => new ConnectorTable(DatasetName, rt.Name, DatasetMetadata) { DisplayName = rt.DisplayName });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
public class ConnectorServiceBase
|
||||
{
|
||||
protected ConnectorServiceBase()
|
||||
{
|
||||
}
|
||||
|
||||
protected internal async Task<T> GetObject<T>(HttpClient httpClient, string message, string uri, CancellationToken cancellationToken, ConnectorLogger logger = null, [CallerMemberName] string callingMethod = "")
|
||||
where T : class, new()
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
string result = await GetObject(httpClient, message, uri, cancellationToken, logger, $"{callingMethod}<{typeof(T).Name}>").ConfigureAwait(false);
|
||||
return string.IsNullOrWhiteSpace(result) ? null : JsonSerializer.Deserialize<T>(result);
|
||||
}
|
||||
|
||||
protected internal async Task<string> GetObject(HttpClient httpClient, string message, string uri, CancellationToken cancellationToken, ConnectorLogger logger = null, [CallerMemberName] string callingMethod = "")
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
string log = $"{callingMethod}.{nameof(GetObject)} for {message}, Uri {uri}";
|
||||
|
||||
try
|
||||
{
|
||||
logger?.LogInformation($"Entering in {log}");
|
||||
|
||||
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string text = response?.Content == null ? string.Empty : await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
int statusCode = (int)response.StatusCode;
|
||||
|
||||
string reasonPhrase = string.IsNullOrEmpty(response.ReasonPhrase) ? string.Empty : $" ({response.ReasonPhrase})";
|
||||
logger?.LogInformation($"Exiting {log}, with Http Status {statusCode}{reasonPhrase}{(statusCode < 300 ? string.Empty : text)}");
|
||||
|
||||
return statusCode < 300 ? text : null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogException(ex, $"Exception in {log}, {ConnectorHelperFunctions.LogException(ex)}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string DoubleEncode(string param)
|
||||
{
|
||||
return HttpUtility.UrlEncode(HttpUtility.UrlEncode(param));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,430 +3,135 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using System.Web;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
// Swagger based CDP tabular service
|
||||
public sealed class ConnectorTable : CdpTabularService
|
||||
{
|
||||
public string Namespace => $"_tbl_{ConnectionId}";
|
||||
// Implements CDP protocol for Tabular connectors
|
||||
public class ConnectorTable : TabularService
|
||||
{
|
||||
public string DisplayName { get; internal set; }
|
||||
|
||||
public string ConnectionId => _connectionId;
|
||||
public string DatasetName { get; protected set; }
|
||||
|
||||
private readonly string _connectionId;
|
||||
private readonly TabularFunctionIdentification _tabularFunctionIdentification;
|
||||
private IReadOnlyList<ConnectorFunction> _tabularFunctions;
|
||||
private readonly OpenApiDocument _openApiDocument;
|
||||
private readonly IReadOnlyDictionary<string, FormulaValue> _globalValues;
|
||||
public string TableName { get; protected set; }
|
||||
|
||||
public ConnectorTable(OpenApiDocument openApiDocument, IReadOnlyDictionary<string, FormulaValue> globalValues)
|
||||
: base()
|
||||
public override bool IsDelegable => TableCapabilities?.IsDelegable ?? false;
|
||||
|
||||
internal ServiceCapabilities TableCapabilities { get; private set; }
|
||||
|
||||
private string _uriPrefix;
|
||||
|
||||
internal DatasetMetadata DatasetMetadata;
|
||||
|
||||
public ConnectorTable(string dataset, string table)
|
||||
{
|
||||
_globalValues = globalValues;
|
||||
_openApiDocument = openApiDocument;
|
||||
_tabularFunctionIdentification = GetTabularFunctions(openApiDocument);
|
||||
|
||||
foreach (string mandatoryGlobalValue in _tabularFunctionIdentification.GlobalValueNames)
|
||||
{
|
||||
if (!globalValues.ContainsKey(mandatoryGlobalValue))
|
||||
{
|
||||
throw new InvalidOperationException($"Missing global value {mandatoryGlobalValue}");
|
||||
}
|
||||
}
|
||||
|
||||
_connectionId = GetString("connectionId", globalValues);
|
||||
|
||||
DatasetName = _tabularFunctionIdentification.DatasetNames.Count == 0 ? string.Join(",", _tabularFunctionIdentification.DatasetNames.Select(dsn => GetString(dsn, globalValues))) : _tabularFunctionIdentification.DefaultDatasetName;
|
||||
TableName = GetString(_tabularFunctionIdentification.TableName, globalValues);
|
||||
DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset));
|
||||
TableName = table ?? throw new ArgumentNullException(nameof(table));
|
||||
}
|
||||
|
||||
internal static TabularFunctionIdentification GetTabularFunctions(OpenApiDocument openApiDocument)
|
||||
{
|
||||
List<SwaggerOperation> swaggerOperations = openApiDocument.Paths.SelectMany((KeyValuePair<string, OpenApiPathItem> path) =>
|
||||
path.Value.Operations.Where((KeyValuePair<OperationType, OpenApiOperation> operation) => !operation.Value.Deprecated)
|
||||
.Select((KeyValuePair<OperationType, OpenApiOperation> operation) => new SwaggerOperation() { OperationType = operation.Key, OperationPath = path.Key, Operation = operation.Value })).ToList();
|
||||
|
||||
// Metadata Service
|
||||
List<SwaggerOperation> metadataServicePotentialOps = new List<SwaggerOperation>();
|
||||
foreach (SwaggerOperation swop in swaggerOperations.Where(o => o.OperationType == OperationType.Get))
|
||||
{
|
||||
Match m = new Regex(@"(/v(?<v>[0-9]{1,2}))?/\$metadata\.json(?<suffix>/datasets/(?<ds>[^/]+)/tables/{(?<tab>[^/]+)})$").Match(swop.OperationPath);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
string v = m.Groups["v"].Value;
|
||||
int i = string.IsNullOrEmpty(v) ? 1 : int.Parse(v, CultureInfo.InvariantCulture);
|
||||
swop.Version = i;
|
||||
|
||||
string ds = m.Groups["ds"].Value;
|
||||
MatchCollection m2 = new Regex(@"{(?<var>[^{}]+)}").Matches(ds);
|
||||
if (m2.Count == 0)
|
||||
{
|
||||
swop.UseDefaultDataset = true;
|
||||
swop.DefaultDatasetName = ds;
|
||||
swop.DatasetNames = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
swop.UseDefaultDataset = false;
|
||||
swop.DefaultDatasetName = null;
|
||||
swop.DatasetNames = m2.Cast<Match>().Select(im => im.Groups["var"].Value).ToArray();
|
||||
}
|
||||
|
||||
swop.TableName = m.Groups["tab"].Value;
|
||||
swop.ServiceBase = m.Groups["suffix"].Value;
|
||||
|
||||
metadataServicePotentialOps.Add(swop);
|
||||
}
|
||||
}
|
||||
|
||||
SwaggerOperation metadataService = GetMaxOperation(metadataServicePotentialOps, "metadata service");
|
||||
|
||||
TabularFunctionIdentification tf = new TabularFunctionIdentification
|
||||
{
|
||||
MetadataServiceOpPath = metadataService.OperationPath,
|
||||
MetadataServiceOpName = OpenApiHelperFunctions.NormalizeOperationId(metadataService.Operation.OperationId),
|
||||
GlobalValueNames = new List<string>() { "connectionId" }
|
||||
};
|
||||
|
||||
if (metadataService.UseDefaultDataset)
|
||||
{
|
||||
tf.DatasetNames = new List<string>();
|
||||
tf.DefaultDatasetName = metadataService.DefaultDatasetName;
|
||||
}
|
||||
else
|
||||
{
|
||||
tf.DatasetNames = metadataService.DatasetNames.ToList();
|
||||
tf.DefaultDatasetName = null;
|
||||
tf.GlobalValueNames.AddRange(metadataService.DatasetNames);
|
||||
}
|
||||
|
||||
tf.TableName = metadataService.TableName;
|
||||
tf.GlobalValueNames.Add(metadataService.TableName);
|
||||
|
||||
string serviceBase = metadataService.ServiceBase;
|
||||
|
||||
// Create
|
||||
List<SwaggerOperation> createPotentialOps = new List<SwaggerOperation>();
|
||||
foreach (SwaggerOperation swop in swaggerOperations.Where(o => o.OperationType == OperationType.Post))
|
||||
{
|
||||
Match m = new Regex(@$"(/v(?<v>[0-9]{{1,2}}))?{EscapeRegex(serviceBase)}/items$").Match(swop.OperationPath);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
string v = m.Groups["v"].Value;
|
||||
int i = string.IsNullOrEmpty(v) ? 1 : int.Parse(v, CultureInfo.InvariantCulture);
|
||||
swop.Version = i;
|
||||
|
||||
createPotentialOps.Add(swop);
|
||||
}
|
||||
}
|
||||
|
||||
SwaggerOperation createOperation = GetMaxOperation(createPotentialOps, "create");
|
||||
|
||||
tf.CreateOpPath = createOperation.OperationPath;
|
||||
tf.CreateOpName = OpenApiHelperFunctions.NormalizeOperationId(createOperation.Operation.OperationId);
|
||||
|
||||
// Read - GetItems
|
||||
List<SwaggerOperation> getItemsPotentialOps = new List<SwaggerOperation>();
|
||||
foreach (SwaggerOperation swop in swaggerOperations.Where(o => o.OperationType == OperationType.Get))
|
||||
{
|
||||
Match m = new Regex(@$"(/v(?<v>[0-9]{{1,2}}))?{EscapeRegex(serviceBase)}/items$").Match(swop.OperationPath);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
string v = m.Groups["v"].Value;
|
||||
int i = string.IsNullOrEmpty(v) ? 1 : int.Parse(v, CultureInfo.InvariantCulture);
|
||||
swop.Version = i;
|
||||
|
||||
getItemsPotentialOps.Add(swop);
|
||||
}
|
||||
}
|
||||
|
||||
SwaggerOperation getItemsOperation = GetMaxOperation(getItemsPotentialOps, "get items");
|
||||
|
||||
tf.GetItemsOpPath = getItemsOperation.OperationPath;
|
||||
tf.GetItemsOpName = OpenApiHelperFunctions.NormalizeOperationId(getItemsOperation.Operation.OperationId);
|
||||
|
||||
// Update
|
||||
List<SwaggerOperation> updatePorentialOps = new List<SwaggerOperation>();
|
||||
foreach (SwaggerOperation swop in swaggerOperations.Where(o => o.OperationType == OperationType.Patch))
|
||||
{
|
||||
Match m = new Regex(@$"(/v(?<v>[0-9]{{1,2}}))?{EscapeRegex(serviceBase)}/items/{{(?<item>[^/]+)}}$").Match(swop.OperationPath);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
string v = m.Groups["v"].Value;
|
||||
int i = string.IsNullOrEmpty(v) ? 1 : int.Parse(v, CultureInfo.InvariantCulture);
|
||||
swop.Version = i;
|
||||
|
||||
swop.ItemName = m.Groups["item"].Value;
|
||||
|
||||
updatePorentialOps.Add(swop);
|
||||
}
|
||||
}
|
||||
|
||||
SwaggerOperation updateOperation = GetMaxOperation(updatePorentialOps, "update item");
|
||||
|
||||
tf.UpdateOpPath = updateOperation.OperationPath;
|
||||
tf.UpdateOpName = OpenApiHelperFunctions.NormalizeOperationId(updateOperation.Operation.OperationId);
|
||||
tf.ItemName = updateOperation.ItemName;
|
||||
|
||||
// Delete
|
||||
List<SwaggerOperation> deletePorentialOps = new List<SwaggerOperation>();
|
||||
foreach (SwaggerOperation swop in swaggerOperations.Where(o => o.OperationType == OperationType.Delete))
|
||||
{
|
||||
Match m = new Regex(@$"(/v(?<v>[0-9]{{1,2}}))?{EscapeRegex(serviceBase)}/items/{{(?<item>[^/]+)}}$").Match(swop.OperationPath);
|
||||
|
||||
if (m.Success)
|
||||
{
|
||||
string v = m.Groups["v"].Value;
|
||||
int i = string.IsNullOrEmpty(v) ? 1 : int.Parse(v, CultureInfo.InvariantCulture);
|
||||
swop.Version = i;
|
||||
|
||||
if (m.Groups["item"].Value == updateOperation.ItemName)
|
||||
{
|
||||
deletePorentialOps.Add(swop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwaggerOperation deleteOperation = GetMaxOperation(deletePorentialOps, "delete item");
|
||||
|
||||
tf.DeleteOpPath = deleteOperation.OperationPath;
|
||||
tf.DeleteOpName = OpenApiHelperFunctions.NormalizeOperationId(deleteOperation.Operation.OperationId);
|
||||
|
||||
return tf;
|
||||
}
|
||||
|
||||
internal class SwaggerOperation
|
||||
{
|
||||
public string ServiceBase;
|
||||
public OperationType OperationType;
|
||||
public string OperationPath;
|
||||
public int Version;
|
||||
public bool UseDefaultDataset;
|
||||
public string DefaultDatasetName;
|
||||
public string[] DatasetNames;
|
||||
public string TableName;
|
||||
public string ItemName;
|
||||
|
||||
internal OpenApiOperation Operation;
|
||||
}
|
||||
|
||||
internal class TabularFunctionIdentification
|
||||
{
|
||||
public List<string> GlobalValueNames;
|
||||
public string MetadataServiceOpPath;
|
||||
public string MetadataServiceOpName;
|
||||
public List<string> DatasetNames;
|
||||
public string DefaultDatasetName;
|
||||
public string TableName;
|
||||
public string CreateOpPath;
|
||||
public string CreateOpName;
|
||||
public string GetItemsOpPath;
|
||||
public string GetItemsOpName;
|
||||
public string UpdateOpPath;
|
||||
public string UpdateOpName;
|
||||
public string DeleteOpPath;
|
||||
public string DeleteOpName;
|
||||
public string ItemName;
|
||||
}
|
||||
|
||||
private static string EscapeRegex(string rex) => rex.Replace("{", @"\{").Replace("}", @"\}");
|
||||
|
||||
private static SwaggerOperation GetMaxOperation(List<SwaggerOperation> potentialOperations, string type)
|
||||
{
|
||||
if (potentialOperations == null || potentialOperations.Count == 0)
|
||||
{
|
||||
throw new Exception($"Unsupported swagger file, cannot identify {type}, function not found");
|
||||
}
|
||||
|
||||
int maxVer = potentialOperations.Max(o => o.Version);
|
||||
IEnumerable<SwaggerOperation> maxOperations = potentialOperations.Where(o => o.Version == maxVer);
|
||||
|
||||
if (maxOperations.Count() != 1)
|
||||
{
|
||||
throw new Exception($"Unsupported swagger file, cannot identify unique {type}");
|
||||
}
|
||||
|
||||
return maxOperations.First();
|
||||
}
|
||||
|
||||
public static string[] GetGlobalValueNames(OpenApiDocument openApiDocument)
|
||||
{
|
||||
return GetTabularFunctions(openApiDocument).GlobalValueNames.ToArray();
|
||||
}
|
||||
|
||||
public static bool IsTabular(IReadOnlyDictionary<string, FormulaValue> globalValues, OpenApiDocument openApiDocument, out string error)
|
||||
{
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
return new ConnectorTable(openApiDocument, globalValues).LoadSwaggerAndIdentifyKeyMethods();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Only used for testing")]
|
||||
internal static (ConnectorFunction s, ConnectorFunction c, ConnectorFunction r, ConnectorFunction u, ConnectorFunction d) GetFunctions(IReadOnlyDictionary<string, FormulaValue> globalValues, OpenApiDocument openApiDocument)
|
||||
{
|
||||
return new ConnectorTable(openApiDocument, globalValues).GetFunctions();
|
||||
}
|
||||
|
||||
public async Task InitAsync(HttpClient httpClient, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
logger?.LogInformation($"Entering in {nameof(ConnectorTable)} {nameof(InitAsync)} for {DatasetName}, {TableName}");
|
||||
|
||||
if (!LoadSwaggerAndIdentifyKeyMethods(logger))
|
||||
{
|
||||
throw new PowerFxConnectorException("Cannot identify tabular methods.");
|
||||
}
|
||||
|
||||
BaseRuntimeConnectorContext runtimeConnectorContext = new RawRuntimeConnectorContext(httpClient);
|
||||
FormulaValue schema = await MetadataService.InvokeAsync(Array.Empty<FormulaValue>(), runtimeConnectorContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger?.LogInformation($"Exiting {nameof(ConnectorTable)} {nameof(InitAsync)} for {DatasetName}, {TableName} {(schema is ErrorValue ev ? string.Join(", ", ev.Errors.Select(er => er.Message)) : string.Empty)}");
|
||||
|
||||
if (schema is StringValue str)
|
||||
{
|
||||
SetTableType(GetSchema(str.Value));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogException(ex, $"Exception in {nameof(ConnectorTable)} {nameof(InitAsync)} for {DatasetName}, {TableName}, {ConnectorHelperFunctions.LogException(ex)}");
|
||||
throw;
|
||||
}
|
||||
internal ConnectorTable(string dataset, string table, DatasetMetadata datasetMetadata)
|
||||
: this(dataset, table)
|
||||
{
|
||||
DatasetMetadata = datasetMetadata;
|
||||
}
|
||||
|
||||
//// TABLE METADATA SERVICE
|
||||
// GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01
|
||||
internal ConnectorFunction MetadataService;
|
||||
public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (IsInitialized)
|
||||
{
|
||||
throw new InvalidOperationException("TabularService already initialized");
|
||||
}
|
||||
|
||||
if (DatasetMetadata == null)
|
||||
{
|
||||
ConnectorDataSource cds = new ConnectorDataSource(DatasetName);
|
||||
await cds.GetDatasetsMetadataAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false);
|
||||
|
||||
DatasetMetadata = cds.DatasetMetadata;
|
||||
|
||||
if (DatasetMetadata == null)
|
||||
{
|
||||
throw new InvalidOperationException("Dataset metadata is not available");
|
||||
}
|
||||
}
|
||||
|
||||
_uriPrefix = uriPrefix;
|
||||
|
||||
string uri = (_uriPrefix ?? string.Empty) +
|
||||
(uriPrefix.Contains("/sql/") ? "/v2" : string.Empty) +
|
||||
$"/$metadata.json/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}/tables/{DoubleEncode(TableName)}?api-version=2015-09-01";
|
||||
|
||||
string text = await GetObject(httpClient, $"Get table metadata", uri, cancellationToken, logger).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
SetTableType(GetSchema(text));
|
||||
}
|
||||
}
|
||||
|
||||
internal RecordType GetSchema(string text)
|
||||
{
|
||||
ConnectorType connectorType = ConnectorFunction.GetConnectorTypeAndTableCapabilities("Schema/Items", FormulaValue.New(text), ConnectorCompatibility.SwaggerCompatibility, out string name, out string displayName, out ServiceCapabilities tableCapabilities);
|
||||
TableName = name;
|
||||
DisplayName = displayName;
|
||||
TableCapabilities = tableCapabilities;
|
||||
|
||||
// Note that connectorType contains columns' capabilities but not the FormulaType (as of current developement)
|
||||
return connectorType?.FormulaType as RecordType;
|
||||
}
|
||||
|
||||
// TABLE DATA SERVICE - CREATE
|
||||
// POST: /datasets/{datasetName}/tables/{tableName}/items?api-version=2015-09-01
|
||||
internal ConnectorFunction CreateItems;
|
||||
|
||||
// TABLE DATA SERVICE - READ
|
||||
// GET AN ITEM - GET: /datasets/{datasetName}/tables/{tableName}/items/{id}?api-version=2015-09-01
|
||||
|
||||
// LIST ITEMS - GET: /datasets/{datasetName}/tables/{tableName}/items?$filter=’CreatedBy’ eq ‘john.doe’&$top=50&$orderby=’Priority’ asc, ’CreationDate’ desc
|
||||
internal ConnectorFunction GetItems;
|
||||
|
||||
// TABLE DATA SERVICE - UPDATE
|
||||
// PATCH: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
internal ConnectorFunction UpdateItem;
|
||||
|
||||
// TABLE DATA SERVICE - DELETE
|
||||
// DELETE: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
internal ConnectorFunction DeleteItem;
|
||||
|
||||
public override Task InitAsync(HttpClient httpClient, string uriPrefix, bool useV2, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
throw new PowerFxConnectorException("Use InitAsync with OpenApiDocument");
|
||||
}
|
||||
|
||||
internal bool LoadSwaggerAndIdentifyKeyMethods(ConnectorLogger logger = null)
|
||||
{
|
||||
var (s, c, r, u, d) = GetFunctions(logger);
|
||||
return s != null && c != null && r != null && u != null && d != null;
|
||||
}
|
||||
|
||||
internal (ConnectorFunction s, ConnectorFunction c, ConnectorFunction r, ConnectorFunction u, ConnectorFunction d) GetFunctions(ConnectorLogger logger = null)
|
||||
{
|
||||
ConnectorSettings connectorSettings = new ConnectorSettings(Namespace)
|
||||
{
|
||||
IncludeInternalFunctions = true,
|
||||
Compatibility = ConnectorCompatibility.SwaggerCompatibility
|
||||
};
|
||||
|
||||
// Swagger based tabular connectors
|
||||
_tabularFunctions = new PowerFxConfig().AddActionConnector(connectorSettings, _openApiDocument, _globalValues, logger);
|
||||
|
||||
MetadataService = _tabularFunctions.FirstOrDefault(tf => tf.Name == _tabularFunctionIdentification.MetadataServiceOpName) ?? throw new PowerFxConnectorException("Metadata service not found.");
|
||||
CreateItems = _tabularFunctions.FirstOrDefault(tf => tf.Name == _tabularFunctionIdentification.CreateOpName) ?? throw new PowerFxConnectorException("Create items service not found.");
|
||||
GetItems = _tabularFunctions.FirstOrDefault(tf => tf.Name == _tabularFunctionIdentification.GetItemsOpName) ?? throw new PowerFxConnectorException("Get items service not found.");
|
||||
UpdateItem = _tabularFunctions.FirstOrDefault(tf => tf.Name == _tabularFunctionIdentification.UpdateOpName) ?? throw new PowerFxConnectorException("Update item service not found.");
|
||||
DeleteItem = _tabularFunctions.FirstOrDefault(tf => tf.Name == _tabularFunctionIdentification.DeleteOpName) ?? throw new PowerFxConnectorException("Delete item service not found.");
|
||||
|
||||
return (MetadataService, CreateItems, GetItems, UpdateItem, DeleteItem);
|
||||
}
|
||||
|
||||
protected override async Task<IReadOnlyCollection<DValue<RecordValue>>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters oDataParameters, CancellationToken cancellationToken)
|
||||
protected override async Task<IReadOnlyCollection<DValue<RecordValue>>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters odataParameters, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ConnectorLogger executionLogger = serviceProvider?.GetService<ConnectorLogger>();
|
||||
HttpClient httpClient = serviceProvider?.GetService<HttpClient>() ?? throw new InvalidOperationException("HttpClient is required on IServiceProvider");
|
||||
|
||||
try
|
||||
Uri uri = new Uri(
|
||||
(_uriPrefix ?? string.Empty) +
|
||||
(_uriPrefix.Contains("/sql/") ? "/v2" : string.Empty) +
|
||||
$"/datasets/{(DatasetMetadata.IsDoubleEncoding ? DoubleEncode(DatasetName) : DatasetName)}/tables/{HttpUtility.UrlEncode(TableName)}/items?api-version=2015-09-01", UriKind.Relative);
|
||||
|
||||
if (odataParameters != null)
|
||||
{
|
||||
executionLogger?.LogInformation($"Entering in {nameof(ConnectorTable)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName}");
|
||||
|
||||
BaseRuntimeConnectorContext runtimeConnectorContext = serviceProvider.GetService<BaseRuntimeConnectorContext>() ?? throw new InvalidOperationException("Cannot determine runtime connector context.");
|
||||
IReadOnlyList<NamedValue> optionalParameters = oDataParameters != null ? oDataParameters.GetNamedValues() : Array.Empty<NamedValue>();
|
||||
|
||||
FormulaValue[] parameters = optionalParameters.Any() ? new FormulaValue[] { FormulaValue.NewRecordFromFields(optionalParameters.ToArray()) } : Array.Empty<FormulaValue>();
|
||||
|
||||
// Notice that there is no paging here, just get 1 page
|
||||
// Use WithRawResults to ignore _getItems return type which is in the form of ![value:*[dynamicProperties:![]]] (ie. without the actual type)
|
||||
FormulaValue rowsRaw = await GetItems.InvokeAsync(parameters, runtimeConnectorContext.WithRawResults(), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
executionLogger?.LogInformation($"Exiting {nameof(ConnectorTable)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName} {(rowsRaw is ErrorValue ev ? string.Join(", ", ev.Errors.Select(er => er.Message)) : string.Empty)}");
|
||||
return rowsRaw is StringValue sv ? GetResult(sv.Value) : Array.Empty<DValue<RecordValue>>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
executionLogger?.LogException(ex, $"Exception in {nameof(ConnectorTable)} {nameof(GetItemsAsync)} for {DatasetName}, {TableName}, {ConnectorHelperFunctions.LogException(ex)}");
|
||||
throw;
|
||||
uri = odataParameters.GetUri(uri);
|
||||
}
|
||||
|
||||
string text = await GetObject(httpClient, $"List items ({nameof(GetItemsInternalAsync)})", uri.ToString(), cancellationToken, executionLogger).ConfigureAwait(false);
|
||||
return !string.IsNullOrWhiteSpace(text) ? GetResult(text) : Array.Empty<DValue<RecordValue>>();
|
||||
}
|
||||
|
||||
private static bool TryGetString(string name, IReadOnlyDictionary<string, FormulaValue> globalValues, out string str)
|
||||
protected IReadOnlyCollection<DValue<RecordValue>> GetResult(string text)
|
||||
{
|
||||
if (globalValues.TryGetValue(name, out FormulaValue fv) && fv is StringValue sv)
|
||||
{
|
||||
str = sv.Value;
|
||||
return !string.IsNullOrEmpty(str);
|
||||
}
|
||||
// $$$ Is this always this type?
|
||||
RecordValue rv = FormulaValueJSON.FromJson(text, RecordType.Empty().Add("value", TableType)) as RecordValue;
|
||||
TableValue tv = rv.Fields.FirstOrDefault(field => field.Name == "value").Value as TableValue;
|
||||
|
||||
str = null;
|
||||
return false;
|
||||
// The call we make contains more fields and we want to remove them here ('@odata.etag')
|
||||
return new InMemoryTableValue(IRContext.NotInSource(TableType), tv.Rows).Rows.ToArray();
|
||||
}
|
||||
|
||||
private static string GetString(string name, IReadOnlyDictionary<string, FormulaValue> globalValues)
|
||||
{
|
||||
return TryGetString(name, globalValues, out string str) ? str : throw new InvalidOperationException($"Cannot determine {name} in global values.");
|
||||
}
|
||||
// TABLE DATA SERVICE - UPDATE
|
||||
// PATCH: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
|
||||
private class RawRuntimeConnectorContext : BaseRuntimeConnectorContext
|
||||
{
|
||||
private readonly HttpMessageInvoker _invoker;
|
||||
|
||||
internal RawRuntimeConnectorContext(HttpMessageInvoker invoker)
|
||||
{
|
||||
_invoker = invoker;
|
||||
}
|
||||
|
||||
internal override bool ReturnRawResults => true;
|
||||
|
||||
public override TimeZoneInfo TimeZoneInfo => TimeZoneInfo.Utc;
|
||||
|
||||
public override HttpMessageInvoker GetInvoker(string @namespace) => _invoker;
|
||||
}
|
||||
// TABLE DATA SERVICE - DELETE
|
||||
// DELETE: /datasets/{datasetName}/tables/{tableName}/items/{id}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
// Used by ConnectorDataSource.GetTablesAsync
|
||||
internal class GetTables
|
||||
{
|
||||
[JsonPropertyName("value")]
|
||||
public List<RawTable> Value { get; set; }
|
||||
}
|
||||
|
||||
internal class RawTable
|
||||
{
|
||||
[JsonPropertyName("Name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("DisplayName")]
|
||||
public string DisplayName { get; set; }
|
||||
}
|
||||
|
||||
// Used by ConnectorDataSource.GetDatasetsMetadataAsync
|
||||
internal class DatasetMetadata
|
||||
{
|
||||
[JsonPropertyName("tabular")]
|
||||
public RawTabular Tabular { get; set; }
|
||||
|
||||
[JsonPropertyName("blob")]
|
||||
public RawBlob Blob { get; set; }
|
||||
|
||||
[JsonPropertyName("datasetFormat")]
|
||||
public string DatasetFormat { get; set; }
|
||||
|
||||
[JsonPropertyName("parameters")]
|
||||
public List<RawDatasetMetadataParameter> Parameters { get; set; }
|
||||
|
||||
public bool IsDoubleEncoding => Tabular?.UrlEncoding == "double";
|
||||
}
|
||||
|
||||
internal class RawTabular
|
||||
{
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonPropertyName("displayName")]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[JsonPropertyName("urlEncoding")]
|
||||
public string UrlEncoding { get; set; }
|
||||
|
||||
[JsonPropertyName("tableDisplayName")]
|
||||
public string TableDisplayName { get; set; }
|
||||
|
||||
[JsonPropertyName("tablePluralName")]
|
||||
public string TablePluralName { get; set; }
|
||||
}
|
||||
|
||||
internal class RawBlob
|
||||
{
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[JsonPropertyName("displayName")]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[JsonPropertyName("urlEncoding")]
|
||||
public string UrlEncoding { get; set; }
|
||||
}
|
||||
|
||||
internal class RawDatasetMetadataParameter
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("urlEncoding")]
|
||||
public string UrlEncoding { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
public bool Required { get; set; }
|
||||
|
||||
[JsonPropertyName("x-ms-summary")]
|
||||
public string XMsSummary { get; set; }
|
||||
|
||||
[JsonPropertyName("x-ms-dynamic-values")]
|
||||
public RawDynamicValues XMsDynamicValues { get; set; }
|
||||
}
|
||||
|
||||
internal class RawDynamicValues
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonPropertyName("value-collection")]
|
||||
public string ValueCollection { get; set; }
|
||||
|
||||
[JsonPropertyName("value-path")]
|
||||
public string ValuePath { get; set; }
|
||||
|
||||
[JsonPropertyName("value-title")]
|
||||
public string ValueTitle { get; set; }
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ using Microsoft.PowerFx.Types;
|
|||
|
||||
namespace Microsoft.PowerFx.Connectors.Tabular
|
||||
{
|
||||
public abstract class TabularService
|
||||
public abstract class TabularService : ConnectorServiceBase
|
||||
{
|
||||
private const string NotInitialized = "Tabular service is not initialized.";
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
public LoggingTestServer(string swaggerName, ITestOutputHelper output, bool live = false)
|
||||
{
|
||||
Live = live;
|
||||
_apiDocument = Helpers.ReadSwagger(swaggerName, output);
|
||||
_apiDocument = string.IsNullOrEmpty(swaggerName) ? null : Helpers.ReadSwagger(swaggerName, output);
|
||||
|
||||
if (live)
|
||||
{
|
||||
|
@ -92,6 +92,7 @@ namespace Microsoft.PowerFx.Tests
|
|||
{
|
||||
var text = GetFileText(filename);
|
||||
SetResponse(text, status);
|
||||
ResponseSetMode = false;
|
||||
}
|
||||
|
||||
private static object GetFileText(string filename)
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -28,46 +26,76 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SQL_Tabular()
|
||||
public async Task SQL_CdpTabular_GetTables()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
using var testConnector = new LoggingTestServer(null /* no swagger */, _output);
|
||||
var config = new PowerFxConfig(Features.PowerFxV1);
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
using var httpClient = new HttpClient(testConnector);
|
||||
string jwt = "eyJ0eXAiOi...";
|
||||
using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", "e74bd8913489439e886426eba8dec1c8", () => jwt, httpClient)
|
||||
{
|
||||
SessionId = "8e67ebdc-d402-455a-b33a-304820832383"
|
||||
};
|
||||
string connectionId = "c1a4e9f52ec94d55bb82f319b3e33a6a";
|
||||
string jwt = "eyJ0eXAiOiJKV1QiL...";
|
||||
using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" };
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("e74bd8913489439e886426eba8dec1c8") },
|
||||
{ "server", FormulaValue.New("pfxdev-sql.database.windows.net") },
|
||||
{ "database", FormulaValue.New("connectortest") },
|
||||
{ "table", FormulaValue.New("Customers") }
|
||||
});
|
||||
ConnectorDataSource cds = new ConnectorDataSource("pfxdev-sql.database.windows.net,connectortest");
|
||||
|
||||
testConnector.SetResponseFromFile(@"Responses\SQL GetDatasetsMetadata.json");
|
||||
await cds.GetDatasetsMetadataAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
|
||||
Assert.NotNull(cds.DatasetMetadata);
|
||||
Assert.Null(cds.DatasetMetadata.Blob);
|
||||
|
||||
Assert.Equal("{server},{database}", cds.DatasetMetadata.DatasetFormat);
|
||||
Assert.NotNull(cds.DatasetMetadata.Tabular);
|
||||
Assert.Equal("dataset", cds.DatasetMetadata.Tabular.DisplayName);
|
||||
Assert.Equal("mru", cds.DatasetMetadata.Tabular.Source);
|
||||
Assert.Equal("Table", cds.DatasetMetadata.Tabular.TableDisplayName);
|
||||
Assert.Equal("Tables", cds.DatasetMetadata.Tabular.TablePluralName);
|
||||
Assert.Equal("single", cds.DatasetMetadata.Tabular.UrlEncoding);
|
||||
Assert.NotNull(cds.DatasetMetadata.Parameters);
|
||||
Assert.Equal(2, cds.DatasetMetadata.Parameters.Count);
|
||||
|
||||
Assert.Equal("Server name.", cds.DatasetMetadata.Parameters[0].Description);
|
||||
Assert.Equal("server", cds.DatasetMetadata.Parameters[0].Name);
|
||||
Assert.True(cds.DatasetMetadata.Parameters[0].Required);
|
||||
Assert.Equal("string", cds.DatasetMetadata.Parameters[0].Type);
|
||||
Assert.Equal("double", cds.DatasetMetadata.Parameters[0].UrlEncoding);
|
||||
Assert.Null(cds.DatasetMetadata.Parameters[0].XMsDynamicValues);
|
||||
Assert.Equal("Server name", cds.DatasetMetadata.Parameters[0].XMsSummary);
|
||||
|
||||
Assert.Equal("Database name.", cds.DatasetMetadata.Parameters[1].Description);
|
||||
Assert.Equal("database", cds.DatasetMetadata.Parameters[1].Name);
|
||||
Assert.True(cds.DatasetMetadata.Parameters[1].Required);
|
||||
Assert.Equal("string", cds.DatasetMetadata.Parameters[1].Type);
|
||||
Assert.Equal("double", cds.DatasetMetadata.Parameters[1].UrlEncoding);
|
||||
Assert.NotNull(cds.DatasetMetadata.Parameters[1].XMsDynamicValues);
|
||||
Assert.Equal("/v2/databases?server={server}", cds.DatasetMetadata.Parameters[1].XMsDynamicValues.Path);
|
||||
Assert.Equal("value", cds.DatasetMetadata.Parameters[1].XMsDynamicValues.ValueCollection);
|
||||
Assert.Equal("Name", cds.DatasetMetadata.Parameters[1].XMsDynamicValues.ValuePath);
|
||||
Assert.Equal("DisplayName", cds.DatasetMetadata.Parameters[1].XMsDynamicValues.ValueTitle);
|
||||
Assert.Equal("Database name", cds.DatasetMetadata.Parameters[1].XMsSummary);
|
||||
|
||||
testConnector.SetResponseFromFile(@"Responses\SQL GetTables.json");
|
||||
IEnumerable<ConnectorTable> tables = await cds.GetTablesAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
|
||||
Assert.NotNull(tables);
|
||||
Assert.Equal(4, tables.Count());
|
||||
Assert.Equal("[dbo].[Customers],[dbo].[Orders],[dbo].[Products],[sys].[database_firewall_rules]", string.Join(",", tables.Select(t => t.TableName)));
|
||||
Assert.Equal("Customers,Orders,Products,sys.database_firewall_rules", string.Join(",", tables.Select(t => t.DisplayName)));
|
||||
|
||||
ConnectorTable connectorTable = tables.First(t => t.DisplayName == "Customers");
|
||||
|
||||
Assert.False(connectorTable.IsInitialized);
|
||||
Assert.Equal("Customers", connectorTable.DisplayName);
|
||||
|
||||
// Use of tabular connector
|
||||
// There is a network call here to retrieve the table's schema
|
||||
testConnector.SetResponseFromFile(@"Responses\SQL Server Load Customers DB.json");
|
||||
ConnectorTable tabularService = new ConnectorTable(apiDoc, globals);
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Customers", tabularService.TableName);
|
||||
Assert.Equal("_tbl_e74bd8913489439e886426eba8dec1c8", tabularService.Namespace);
|
||||
await connectorTable.InitAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(connectorTable.IsInitialized);
|
||||
|
||||
await tabularService.InitAsync(client, CancellationToken.None, new ConsoleLogger(_output)).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
Assert.Equal("Customers", tabularService.Name);
|
||||
Assert.Equal("Customers", tabularService.DisplayName);
|
||||
|
||||
ConnectorTableValue sqlTable = tabularService.GetTableValue();
|
||||
ConnectorTableValue sqlTable = connectorTable.GetTableValue();
|
||||
Assert.True(sqlTable._tabularService.IsInitialized);
|
||||
Assert.True(sqlTable.IsDelegable);
|
||||
|
||||
//Assert.True(sqlTable.Is)
|
||||
Assert.Equal("*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type._type.ToString());
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
@ -78,8 +106,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Customers", sqlTable);
|
||||
BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext(tabularService.Namespace, client, console: _output);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddRuntimeContext(runtimeContext);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues)
|
||||
.AddService<ConnectorLogger>(logger)
|
||||
.AddService<HttpClient>(client);
|
||||
|
||||
// Expression with tabular connector
|
||||
string expr = @"First(Customers).Address";
|
||||
|
@ -98,11 +127,10 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.Equal("Juigné", address.Value);
|
||||
|
||||
// Rows are not cached here as the cache is stored in ConnectorTableValueWithServiceProvider which is created by InjectServiceProviderFunction, itself added during Engine.Check
|
||||
testConnector.SetResponseFromFiles(@"Responses\SQL Server Get First Customers.json", @"Responses\SQL Server Get First Customers.json");
|
||||
result = await engine.EvalAsync("First(Customers).Phone; Last(Customers).Phone", CancellationToken.None, options: new ParserOptions() { AllowsSideEffects = true }, runtimeConfig: rc).ConfigureAwait(false);
|
||||
testConnector.SetResponseFromFile(@"Responses\SQL Server Get First Customers.json");
|
||||
result = await engine.EvalAsync("Last(Customers).Phone", CancellationToken.None, runtimeConfig: rc).ConfigureAwait(false);
|
||||
StringValue phone = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("+1-425-705-0000", phone.Value);
|
||||
Assert.Equal(2, testConnector.CurrentResponse); // This confirms we had 2 network calls
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -126,12 +154,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
testConnector.SetResponseFromFile(@"Responses\SQL Server Load Customers DB.json");
|
||||
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
CdpTabularService tabularService = new CdpTabularService("pfxdev-sql.database.windows.net,connectortest", "Customers");
|
||||
ConnectorTable tabularService = new ConnectorTable("pfxdev-sql.database.windows.net,connectortest", "Customers");
|
||||
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Customers", tabularService.TableName);
|
||||
|
||||
await tabularService.InitAsync(client, $"/apim/sql/{connectionId}", true, CancellationToken.None, logger).ConfigureAwait(false);
|
||||
testConnector.SetResponseFromFiles(@"Responses\SQL GetDatasetsMetadata.json", @"Responses\SQL Server Load Customers DB.json");
|
||||
await tabularService.InitAsync(client, $"/apim/sql/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
|
||||
ConnectorTableValue sqlTable = tabularService.GetTableValue();
|
||||
|
@ -175,90 +204,55 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
[Obsolete("Using SwaggerTabularService.GetFunctions")]
|
||||
public async Task SQL_Tabular_CheckParams()
|
||||
public async Task SP_CdpTabular_GetTables()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SQL Server.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
|
||||
string[] globalValueNames = ConnectorTable.GetGlobalValueNames(apiDoc);
|
||||
Assert.Equal("connectionId, database, server, table", string.Join(", ", globalValueNames.OrderBy(x => x)));
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("e74bd8913489439e886426eba8dec1c8") },
|
||||
{ "server", FormulaValue.New("pfxdev-sql.database.windows.net") },
|
||||
{ "database", FormulaValue.New("connectortest") },
|
||||
{ "table", FormulaValue.New("Customers") }
|
||||
});
|
||||
|
||||
bool isTabularService = ConnectorTable.IsTabular(globals, apiDoc, out string error);
|
||||
Assert.True(isTabularService);
|
||||
|
||||
var (s, c, r, u, d) = ConnectorTable.GetFunctions(globals, apiDoc);
|
||||
|
||||
Assert.Equal("GetTableV2", s.Name);
|
||||
Assert.Equal("PostItemV2", c.Name);
|
||||
Assert.Equal("GetItemsV2", r.Name);
|
||||
Assert.Equal("PatchItemV2", u.Name);
|
||||
Assert.Equal("DeleteItemV2", d.Name);
|
||||
|
||||
Assert.Equal(@"/apim/sql/{connectionId}/v2/$metadata.json/datasets/{server},{database}/tables/{table}", s.OperationPath);
|
||||
Assert.Equal(@"/apim/sql/{connectionId}/v2/datasets/{server},{database}/tables/{table}/items", c.OperationPath);
|
||||
Assert.Equal(@"/apim/sql/{connectionId}/v2/datasets/{server},{database}/tables/{table}/items", r.OperationPath);
|
||||
Assert.Equal(@"/apim/sql/{connectionId}/v2/datasets/{server},{database}/tables/{table}/items/{id}", u.OperationPath);
|
||||
Assert.Equal(@"/apim/sql/{connectionId}/v2/datasets/{server},{database}/tables/{table}/items/{id}", d.OperationPath);
|
||||
|
||||
Assert.Equal("GET", s.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("POST", c.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("GET", r.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("PATCH", u.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("DELETE", d.HttpMethod.Method.ToUpperInvariant());
|
||||
|
||||
Assert.Equal(string.Empty, string.Join(", ", s.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("item", string.Join(", ", c.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal(string.Empty, string.Join(", ", r.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id, item", string.Join(", ", u.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id", string.Join(", ", d.RequiredParameters.Select(rp => rp.Name)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SP_Tabular()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
using var testConnector = new LoggingTestServer(null /* no swagger */, _output);
|
||||
var config = new PowerFxConfig(Features.PowerFxV1);
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
using var httpClient = new HttpClient(testConnector);
|
||||
string jwt = "eyJ0eXAiO...";
|
||||
using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", "0b905132239e463a9d12f816be201da9", () => jwt, httpClient)
|
||||
{
|
||||
SessionId = "8e67ebdc-d402-455a-b33a-304820832384"
|
||||
};
|
||||
string connectionId = "3738993883dc406d86802d8a6a923d3e";
|
||||
string jwt = "eyJ0eXAiOiJK...";
|
||||
using var client = new PowerPlatformConnectorClient("firstrelease-003.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" };
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("0b905132239e463a9d12f816be201da9") },
|
||||
{ "dataset", FormulaValue.New("https://microsofteur.sharepoint.com/teams/pfxtest") },
|
||||
{ "table", FormulaValue.New("Documents") },
|
||||
});
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
ConnectorDataSource cds = new ConnectorDataSource("https://microsofteur.sharepoint.com/teams/pfxtest");
|
||||
|
||||
// Use of tabular connector
|
||||
// There is a network call here to retrieve the table's schema
|
||||
testConnector.SetResponseFromFile(@"Responses\SP GetTable.json");
|
||||
testConnector.SetResponseFromFiles(@"Responses\SP GetDatasetsMetadata.json", @"Responses\SP GetTables.json");
|
||||
IEnumerable<ConnectorTable> tables = await cds.GetTablesAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
|
||||
ConnectorTable tabularService = new ConnectorTable(apiDoc, globals);
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Documents", tabularService.TableName);
|
||||
Assert.Equal("_tbl_0b905132239e463a9d12f816be201da9", tabularService.Namespace);
|
||||
Assert.NotNull(cds.DatasetMetadata);
|
||||
|
||||
await tabularService.InitAsync(client, CancellationToken.None, new ConsoleLogger(_output)).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
Assert.Equal("Documents", tabularService.Name);
|
||||
Assert.Equal("Documents", tabularService.DisplayName);
|
||||
Assert.NotNull(cds.DatasetMetadata.Blob);
|
||||
Assert.Equal("mru", cds.DatasetMetadata.Blob.Source);
|
||||
Assert.Equal("site", cds.DatasetMetadata.Blob.DisplayName);
|
||||
Assert.Equal("double", cds.DatasetMetadata.Blob.UrlEncoding);
|
||||
|
||||
ConnectorTableValue spTable = tabularService.GetTableValue();
|
||||
Assert.Null(cds.DatasetMetadata.DatasetFormat);
|
||||
Assert.Null(cds.DatasetMetadata.Parameters);
|
||||
|
||||
Assert.NotNull(cds.DatasetMetadata.Tabular);
|
||||
Assert.Equal("site", cds.DatasetMetadata.Tabular.DisplayName);
|
||||
Assert.Equal("mru", cds.DatasetMetadata.Tabular.Source);
|
||||
Assert.Equal("list", cds.DatasetMetadata.Tabular.TableDisplayName);
|
||||
Assert.Equal("lists", cds.DatasetMetadata.Tabular.TablePluralName);
|
||||
Assert.Equal("double", cds.DatasetMetadata.Tabular.UrlEncoding);
|
||||
|
||||
Assert.NotNull(tables);
|
||||
Assert.Equal(2, tables.Count());
|
||||
Assert.Equal("4bd37916-0026-4726-94e8-5a0cbc8e476a,5266fcd9-45ef-4b8f-8014-5d5c397db6f0", string.Join(",", tables.Select(t => t.TableName)));
|
||||
Assert.Equal("Documents,MikeTestList", string.Join(",", tables.Select(t => t.DisplayName)));
|
||||
|
||||
ConnectorTable connectorTable = tables.First(t => t.DisplayName == "Documents");
|
||||
|
||||
Assert.False(connectorTable.IsInitialized);
|
||||
Assert.Equal("4bd37916-0026-4726-94e8-5a0cbc8e476a", connectorTable.TableName);
|
||||
|
||||
testConnector.SetResponseFromFiles(@"Responses\SP GetTable.json");
|
||||
await connectorTable.InitAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(connectorTable.IsInitialized);
|
||||
|
||||
ConnectorTableValue spTable = connectorTable.GetTableValue();
|
||||
Assert.True(spTable._tabularService.IsInitialized);
|
||||
Assert.True(spTable.IsDelegable);
|
||||
|
||||
|
@ -278,8 +272,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Documents", spTable);
|
||||
BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext(tabularService.Namespace, client, console: _output);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddRuntimeContext(runtimeContext);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues)
|
||||
.AddService<ConnectorLogger>(logger)
|
||||
.AddService<HttpClient>(client);
|
||||
|
||||
// Expression with tabular connector
|
||||
string expr = @"First(Documents).Name";
|
||||
|
@ -310,11 +305,11 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
[Fact]
|
||||
public async Task SP_CdpTabular()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
using var testConnector = new LoggingTestServer(null /* no swagger */, _output);
|
||||
var config = new PowerFxConfig(Features.PowerFxV1);
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
using var httpClient = new HttpClient(testConnector);
|
||||
string connectionId = "0b905132239e463a9d12f816be201da9";
|
||||
string jwt = "eyJ0eXAiOiJKV....";
|
||||
|
@ -323,16 +318,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
SessionId = "8e67ebdc-d402-455a-b33a-304820832384"
|
||||
};
|
||||
|
||||
// Use of tabular connector
|
||||
// There is a network call here to retrieve the table's schema
|
||||
testConnector.SetResponseFromFile(@"Responses\SP GetTable.json");
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
CdpTabularService tabularService = new CdpTabularService("https://microsofteur.sharepoint.com/teams/pfxtest", "Documents");
|
||||
ConnectorTable tabularService = new ConnectorTable("https://microsofteur.sharepoint.com/teams/pfxtest", "Documents");
|
||||
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Documents", tabularService.TableName);
|
||||
|
||||
await tabularService.InitAsync(client, $"/apim/sharepointonline/{connectionId}", false, CancellationToken.None, logger).ConfigureAwait(false);
|
||||
testConnector.SetResponseFromFiles(@"Responses\SP GetDatasetsMetadata.json", @"Responses\SP GetTable.json");
|
||||
await tabularService.InitAsync(client, $"/apim/sharepointonline/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
|
||||
ConnectorTableValue spTable = tabularService.GetTableValue();
|
||||
|
@ -386,121 +378,50 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SP_Tabular_MissingParam()
|
||||
public async Task SF_CdpTabular_GetTables()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("0b905132239e463a9d12f816be201da9") },
|
||||
{ "table", FormulaValue.New("Documents") },
|
||||
});
|
||||
|
||||
bool isTabularService = ConnectorTable.IsTabular(globals, apiDoc, out string error);
|
||||
Assert.False(isTabularService);
|
||||
Assert.Equal("Missing global value dataset", error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SP_Tabular_MissingParam2()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("0b905132239e463a9d12f816be201da9") },
|
||||
{ "dataset", FormulaValue.New("https://microsofteur.sharepoint.com/teams/pfxtest") },
|
||||
});
|
||||
|
||||
bool isTabularService = ConnectorTable.IsTabular(globals, apiDoc, out string error);
|
||||
Assert.False(isTabularService);
|
||||
Assert.Equal("Missing global value table", error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Obsolete("Using SwaggerTabularService.GetFunctions")]
|
||||
public async Task SP_Tabular_CheckParams()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SharePoint.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
|
||||
string[] globalValueNames = ConnectorTable.GetGlobalValueNames(apiDoc);
|
||||
Assert.Equal("connectionId, dataset, table", string.Join(", ", globalValueNames.OrderBy(x => x)));
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("0b905132239e463a9d12f816be201da9") },
|
||||
{ "dataset", FormulaValue.New("https://microsofteur.sharepoint.com/teams/pfxtest") },
|
||||
{ "table", FormulaValue.New("Documents") },
|
||||
});
|
||||
|
||||
bool isTabularService = ConnectorTable.IsTabular(globals, apiDoc, out string error);
|
||||
Assert.True(isTabularService);
|
||||
|
||||
var (s, c, r, u, d) = ConnectorTable.GetFunctions(globals, apiDoc);
|
||||
|
||||
Assert.Equal("GetTable", s.Name);
|
||||
Assert.Equal("PostItem", c.Name);
|
||||
Assert.Equal("GetItems", r.Name);
|
||||
Assert.Equal("PatchItem", u.Name);
|
||||
Assert.Equal("DeleteItem", d.Name);
|
||||
|
||||
Assert.Equal(@"/apim/sharepointonline/{connectionId}/$metadata.json/datasets/{dataset}/tables/{table}", s.OperationPath);
|
||||
Assert.Equal(@"/apim/sharepointonline/{connectionId}/datasets/{dataset}/tables/{table}/items", c.OperationPath);
|
||||
Assert.Equal(@"/apim/sharepointonline/{connectionId}/datasets/{dataset}/tables/{table}/items", r.OperationPath);
|
||||
Assert.Equal(@"/apim/sharepointonline/{connectionId}/datasets/{dataset}/tables/{table}/items/{id}", u.OperationPath);
|
||||
Assert.Equal(@"/apim/sharepointonline/{connectionId}/datasets/{dataset}/tables/{table}/items/{id}", d.OperationPath);
|
||||
|
||||
Assert.Equal("GET", s.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("POST", c.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("GET", r.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("PATCH", u.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("DELETE", d.HttpMethod.Method.ToUpperInvariant());
|
||||
|
||||
Assert.Equal(string.Empty, string.Join(", ", s.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("item", string.Join(", ", c.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal(string.Empty, string.Join(", ", r.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id, item", string.Join(", ", u.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id", string.Join(", ", d.RequiredParameters.Select(rp => rp.Name)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SF_Tabular()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SalesForce.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
using var testConnector = new LoggingTestServer(null /* no swagger */, _output);
|
||||
var config = new PowerFxConfig(Features.PowerFxV1);
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
using var httpClient = new HttpClient(testConnector);
|
||||
string jwt = "eyJ0eXAi...";
|
||||
using var client = new PowerPlatformConnectorClient("tip2-001.azure-apihub.net", "53d7f409-4bce-e458-8245-5fa1346ec433", "ec5fe6d1cad744a0a716fe4597a74b2e", () => jwt, httpClient)
|
||||
{
|
||||
SessionId = "8e67ebdc-d402-455a-b33a-304820832384"
|
||||
};
|
||||
string connectionId = "e88a5bf3321547e0965695384a2fe57d";
|
||||
string jwt = "eyJ0eXAiOiJKV1Qi...";
|
||||
using var client = new PowerPlatformConnectorClient("tip2-001.azure-apihub.net", "8d626c93-244c-eaa5-b3d8-bbffbb04b626", connectionId, () => jwt, httpClient) { SessionId = "8e67ebdc-d402-455a-b33a-304820832383" };
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("ec5fe6d1cad744a0a716fe4597a74b2e") },
|
||||
{ "table", FormulaValue.New("Account") },
|
||||
});
|
||||
ConnectorDataSource cds = new ConnectorDataSource("default");
|
||||
|
||||
// Use of tabular connector
|
||||
ConnectorTable tabularService = new ConnectorTable(apiDoc, globals);
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Account", tabularService.TableName);
|
||||
Assert.Equal("_tbl_ec5fe6d1cad744a0a716fe4597a74b2e", tabularService.Namespace);
|
||||
testConnector.SetResponseFromFile(@"Responses\SF GetDatasetsMetadata.json");
|
||||
await cds.GetDatasetsMetadataAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
|
||||
Assert.NotNull(cds.DatasetMetadata);
|
||||
Assert.Null(cds.DatasetMetadata.Blob);
|
||||
Assert.Null(cds.DatasetMetadata.DatasetFormat);
|
||||
Assert.Null(cds.DatasetMetadata.Parameters);
|
||||
|
||||
Assert.NotNull(cds.DatasetMetadata.Tabular);
|
||||
Assert.Equal("dataset", cds.DatasetMetadata.Tabular.DisplayName);
|
||||
Assert.Equal("singleton", cds.DatasetMetadata.Tabular.Source);
|
||||
Assert.Equal("Table", cds.DatasetMetadata.Tabular.TableDisplayName);
|
||||
Assert.Equal("Tables", cds.DatasetMetadata.Tabular.TablePluralName);
|
||||
Assert.Equal("double", cds.DatasetMetadata.Tabular.UrlEncoding);
|
||||
|
||||
testConnector.SetResponseFromFile(@"Responses\SF GetTables.json");
|
||||
IEnumerable<ConnectorTable> tables = await cds.GetTablesAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
|
||||
Assert.NotNull(tables);
|
||||
Assert.Equal(569, tables.Count());
|
||||
|
||||
ConnectorTable connectorTable = tables.First(t => t.DisplayName == "Accounts");
|
||||
Assert.Equal("Account", connectorTable.TableName);
|
||||
Assert.False(connectorTable.IsInitialized);
|
||||
|
||||
// There is a network call here to retrieve the table's schema
|
||||
testConnector.SetResponseFromFile(@"Responses\SF GetSchema.json");
|
||||
await tabularService.InitAsync(client, CancellationToken.None, new ConsoleLogger(_output)).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
Assert.Equal("Account", tabularService.Name);
|
||||
Assert.Equal("Accounts", tabularService.DisplayName);
|
||||
await connectorTable.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(connectorTable.IsInitialized);
|
||||
|
||||
ConnectorTableValue sfTable = tabularService.GetTableValue();
|
||||
ConnectorTableValue sfTable = connectorTable.GetTableValue();
|
||||
Assert.True(sfTable._tabularService.IsInitialized);
|
||||
Assert.True(sfTable.IsDelegable);
|
||||
|
||||
|
@ -521,8 +442,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Accounts", sfTable);
|
||||
BaseRuntimeConnectorContext runtimeContext = new TestConnectorRuntimeContext(tabularService.Namespace, client, console: _output);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddRuntimeContext(runtimeContext);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues)
|
||||
.AddService<ConnectorLogger>(logger)
|
||||
.AddService<HttpClient>(client);
|
||||
|
||||
// Expression with tabular connector
|
||||
string expr = @"First(Accounts).'Account ID'";
|
||||
|
@ -544,18 +466,18 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
testConnector.SetResponseFromFile(@"Responses\SF GetData.json");
|
||||
FormulaValue result = await check.GetEvaluator().EvalAsync(CancellationToken.None, rc).ConfigureAwait(false);
|
||||
|
||||
StringValue docName = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("001DR00001Xj1YmYAJ", docName.Value);
|
||||
StringValue accountId = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("001DR00001Xj1YmYAJ", accountId.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SF_CdpTabular()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SalesForce.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
using var testConnector = new LoggingTestServer(null /* no swagger */, _output);
|
||||
var config = new PowerFxConfig(Features.PowerFxV1);
|
||||
var engine = new RecalcEngine(config);
|
||||
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
using var httpClient = new HttpClient(testConnector);
|
||||
string connectionId = "ec5fe6d1cad744a0a716fe4597a74b2e";
|
||||
string jwt = "eyJ0eXAiOiJ...";
|
||||
|
@ -564,16 +486,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
SessionId = "8e67ebdc-d402-455a-b33a-304820832384"
|
||||
};
|
||||
|
||||
// Use of tabular connector
|
||||
// There is a network call here to retrieve the table's schema
|
||||
testConnector.SetResponseFromFile(@"Responses\SF GetSchema.json");
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
CdpTabularService tabularService = new CdpTabularService("default", "Account");
|
||||
ConnectorTable tabularService = new ConnectorTable("default", "Account");
|
||||
|
||||
Assert.False(tabularService.IsInitialized);
|
||||
Assert.Equal("Account", tabularService.TableName);
|
||||
|
||||
await tabularService.InitAsync(client, $"/apim/salesforce/{connectionId}", false, CancellationToken.None, logger).ConfigureAwait(false);
|
||||
testConnector.SetResponseFromFiles(@"Responses\SF GetDatasetsMetadata.json", @"Responses\SF GetSchema.json");
|
||||
await tabularService.InitAsync(client, $"/apim/salesforce/{connectionId}", CancellationToken.None, logger).ConfigureAwait(false);
|
||||
Assert.True(tabularService.IsInitialized);
|
||||
|
||||
ConnectorTableValue sfTable = tabularService.GetTableValue();
|
||||
|
@ -624,51 +543,5 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
StringValue accountId = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("001DR00001Xj1YmYAJ", accountId.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Obsolete("Using SwaggerTabularService.GetFunctions")]
|
||||
public async Task SF_Tabular_CheckParams()
|
||||
{
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\SalesForce.json", _output);
|
||||
var apiDoc = testConnector._apiDocument;
|
||||
|
||||
string[] globalValueNames = ConnectorTable.GetGlobalValueNames(apiDoc);
|
||||
Assert.Equal("connectionId, table", string.Join(", ", globalValueNames.OrderBy(x => x)));
|
||||
|
||||
IReadOnlyDictionary<string, FormulaValue> globals = new ReadOnlyDictionary<string, FormulaValue>(new Dictionary<string, FormulaValue>()
|
||||
{
|
||||
{ "connectionId", FormulaValue.New("0b905132239e463a9d12f816be201da9") },
|
||||
{ "table", FormulaValue.New("Documents") },
|
||||
});
|
||||
|
||||
bool isTabularService = ConnectorTable.IsTabular(globals, apiDoc, out string error);
|
||||
Assert.True(isTabularService, error);
|
||||
|
||||
var (s, c, r, u, d) = ConnectorTable.GetFunctions(globals, apiDoc);
|
||||
|
||||
Assert.Equal("GetTable", s.Name);
|
||||
Assert.Equal("PostItemV2", c.Name);
|
||||
Assert.Equal("GetItems", r.Name);
|
||||
Assert.Equal("PatchItemV3", u.Name);
|
||||
Assert.Equal("DeleteItem", d.Name);
|
||||
|
||||
Assert.Equal(@"/apim/salesforce/{connectionId}/$metadata.json/datasets/default/tables/{table}", s.OperationPath);
|
||||
Assert.Equal(@"/apim/salesforce/{connectionId}/v2/datasets/default/tables/{table}/items", c.OperationPath);
|
||||
Assert.Equal(@"/apim/salesforce/{connectionId}/datasets/default/tables/{table}/items", r.OperationPath);
|
||||
Assert.Equal(@"/apim/salesforce/{connectionId}/v3/datasets/default/tables/{table}/items/{id}", u.OperationPath);
|
||||
Assert.Equal(@"/apim/salesforce/{connectionId}/datasets/default/tables/{table}/items/{id}", d.OperationPath);
|
||||
|
||||
Assert.Equal("GET", s.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("POST", c.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("GET", r.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("PATCH", u.HttpMethod.Method.ToUpperInvariant());
|
||||
Assert.Equal("DELETE", d.HttpMethod.Method.ToUpperInvariant());
|
||||
|
||||
Assert.Equal(string.Empty, string.Join(", ", s.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("item", string.Join(", ", c.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal(string.Empty, string.Join(", ", r.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id, item", string.Join(", ", u.RequiredParameters.Select(rp => rp.Name)));
|
||||
Assert.Equal("id", string.Join(", ", d.RequiredParameters.Select(rp => rp.Name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ namespace Microsoft.PowerFx.Connector.Tests
|
|||
{
|
||||
var asm = typeof(PowerPlatformConnectorClient).Assembly;
|
||||
|
||||
// The goal for public namespaces is to make the SDK easy for the consumer.
|
||||
// Namespace principles for public classes: //
|
||||
// The goal for public namespaces is to make the SDK easy for the consumer.
|
||||
// Namespace principles for public classes: //
|
||||
// - prefer fewer namespaces. See C# for example: https://docs.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis
|
||||
// - For easy discovery, but Engine in "Microsoft.PowerFx".
|
||||
// - For sub areas with many related classes, cluster into a single subnamespace.
|
||||
|
@ -33,26 +33,27 @@ namespace Microsoft.PowerFx.Connector.Tests
|
|||
"Microsoft.PowerFx.Connectors.ConnectorLog",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorLogger",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorParameter",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorParameters",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorParameters",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorParameterWithSuggestions",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorSchema",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorSettings",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorTableValue",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorSettings",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorTableValue",
|
||||
"Microsoft.PowerFx.Connectors.ConnectorType",
|
||||
"Microsoft.PowerFx.Connectors.Constants",
|
||||
"Microsoft.PowerFx.Connectors.EngineExtensions",
|
||||
"Microsoft.PowerFx.Connectors.LogCategory",
|
||||
"Microsoft.PowerFx.Connectors.MediaKind",
|
||||
"Microsoft.PowerFx.Connectors.ODataParameters",
|
||||
"Microsoft.PowerFx.Connectors.OpenApiExtensions",
|
||||
"Microsoft.PowerFx.Connectors.ODataParameters",
|
||||
"Microsoft.PowerFx.Connectors.OpenApiExtensions",
|
||||
"Microsoft.PowerFx.Connectors.OpenApiParser",
|
||||
"Microsoft.PowerFx.Connectors.PowerFxConnectorException",
|
||||
"Microsoft.PowerFx.Connectors.PowerPlatformConnectorClient",
|
||||
"Microsoft.PowerFx.Connectors.RuntimeConfigExtensions",
|
||||
"Microsoft.PowerFx.Connectors.RuntimeConnectorContextExtensions",
|
||||
"Microsoft.PowerFx.Connectors.SupportsConnectorErrors",
|
||||
"Microsoft.PowerFx.Connectors.Tabular.ConnectorDataSource",
|
||||
"Microsoft.PowerFx.Connectors.Tabular.ConnectorServiceBase",
|
||||
"Microsoft.PowerFx.Connectors.Tabular.ConnectorTable",
|
||||
"Microsoft.PowerFx.Connectors.Tabular.CdpTabularService",
|
||||
"Microsoft.PowerFx.Connectors.Tabular.TabularService",
|
||||
"Microsoft.PowerFx.Connectors.Visibility"
|
||||
};
|
||||
|
@ -73,7 +74,7 @@ namespace Microsoft.PowerFx.Connector.Tests
|
|||
|
||||
Assert.True(count == 0, $"Unexpected public types: {sb}");
|
||||
|
||||
// Types we expect to be in the assembly are all there.
|
||||
// Types we expect to be in the assembly are all there.
|
||||
Assert.Empty(allowed);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"tabular": {
|
||||
"source": "singleton",
|
||||
"displayName": "dataset",
|
||||
"urlEncoding": "double",
|
||||
"tableDisplayName": "Table",
|
||||
"tablePluralName": "Tables"
|
||||
},
|
||||
"blob": null
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"tabular": {
|
||||
"source": "mru",
|
||||
"displayName": "site",
|
||||
"urlEncoding": "double",
|
||||
"tableDisplayName": "list",
|
||||
"tablePluralName": "lists"
|
||||
},
|
||||
"blob": {
|
||||
"source": "mru",
|
||||
"displayName": "site",
|
||||
"urlEncoding": "double"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"value": [
|
||||
{
|
||||
"Name": "4bd37916-0026-4726-94e8-5a0cbc8e476a",
|
||||
"DisplayName": "Documents",
|
||||
"Type": "101"
|
||||
},
|
||||
{
|
||||
"Name": "5266fcd9-45ef-4b8f-8014-5d5c397db6f0",
|
||||
"DisplayName": "MikeTestList",
|
||||
"Type": "100"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"tabular": {
|
||||
"source": "mru",
|
||||
"displayName": "dataset",
|
||||
"urlEncoding": "single",
|
||||
"tableDisplayName": "Table",
|
||||
"tablePluralName": "Tables"
|
||||
},
|
||||
"blob": null,
|
||||
"datasetFormat": "{server},{database}",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "server",
|
||||
"type": "string",
|
||||
"urlEncoding": "double",
|
||||
"description": "Server name.",
|
||||
"required": true,
|
||||
"x-ms-summary": "Server name"
|
||||
},
|
||||
{
|
||||
"name": "database",
|
||||
"type": "string",
|
||||
"urlEncoding": "double",
|
||||
"description": "Database name.",
|
||||
"required": true,
|
||||
"x-ms-summary": "Database name",
|
||||
"x-ms-dynamic-values": {
|
||||
"path": "/v2/databases?server={server}",
|
||||
"value-collection": "value",
|
||||
"value-path": "Name",
|
||||
"value-title": "DisplayName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"@odata.context": "https://sql-wus2.azconn-wus2-001.p.azurewebsites.net/v2/$metadata#datasets('pfxdev-sql.database.windows.net%2Cconnectortest')/tables",
|
||||
"value": [
|
||||
{
|
||||
"Name": "[dbo].[Customers]",
|
||||
"DisplayName": "Customers"
|
||||
},
|
||||
{
|
||||
"Name": "[dbo].[Orders]",
|
||||
"DisplayName": "Orders"
|
||||
},
|
||||
{
|
||||
"Name": "[dbo].[Products]",
|
||||
"DisplayName": "Products"
|
||||
},
|
||||
{
|
||||
"Name": "[sys].[database_firewall_rules]",
|
||||
"DisplayName": "sys.database_firewall_rules"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -6,15 +6,7 @@
|
|||
"description": "Microsoft SQL Server is a relational database management system developed by Microsoft. Connect to SQL Server to manage data. You can perform various actions such as create, update, get, and delete on rows in a table.",
|
||||
"x-ms-api-annotation": {
|
||||
"status": "Production"
|
||||
},
|
||||
"__comments00": " --- Changes applied to this file ---",
|
||||
"__comments01": "",
|
||||
"__comments02": " 1. basePath is /apim/sql instead of / only",
|
||||
"__comments03": "",
|
||||
"__comments04": " 2. All paths are prefixed with /{connectionId}",
|
||||
"__comments05": "",
|
||||
"__comments06": " 3. Added fake 'ExecuteProcedure_V2z' operation for testing",
|
||||
"__comments07": ""
|
||||
}
|
||||
},
|
||||
"host": "localhost:33069",
|
||||
"basePath": "/apim/sql",
|
||||
|
|
Загрузка…
Ссылка в новой задаче