no swagger
no v2
add ConnectorDataSource
get Dataset Metadata
This commit is contained in:
Luc Genetier 2024-05-15 08:30:24 -07:00 коммит произвёл GitHub
Родитель ebe58cf32a
Коммит f1afe8c523
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
16 изменённых файлов: 2864 добавлений и 852 удалений

Просмотреть файл

@ -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",