зеркало из https://github.com/microsoft/Power-Fx.git
Refactor CDP implementation and add Capabilities (#2636)
Refactor CDP implementation Use AssociatedDataSources for CDP record types Make all CDP records lazy Add x-ms-enum management New TabularRecordType (lazy) Make CdpTableResolver return ConnectorType Remove CdpDType hack Remove CdpTableType hack Remove CdpTableDescriptor Remove ExternalCdpDataSource Remove ICdpTableResolver.GenerateADS hack Rework Connector service capabilities Add Relationships to CdpService Add TableParameters to CdpTable New InternalTableMetadata and related classes in Pfx Core New InternalTableParameters in Pfx Core (implementing IExternalTabularDataSource) Fix DType ToRecord, ToTable
This commit is contained in:
Родитель
d3c93a3869
Коммит
0fd9e3e130
|
@ -10,6 +10,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
public const string XMsDynamicProperties = "x-ms-dynamic-properties";
|
||||
public const string XMsDynamicSchema = "x-ms-dynamic-schema";
|
||||
public const string XMsDynamicValues = "x-ms-dynamic-values";
|
||||
public const string XMsEnum = "x-ms-enum";
|
||||
public const string XMsEnumDisplayName = "x-ms-enum-display-name";
|
||||
public const string XMsEnumValues = "x-ms-enum-values";
|
||||
public const string XMsExplicitInput = "x-ms-explicit-input";
|
||||
|
|
|
@ -17,6 +17,7 @@ using Microsoft.OpenApi.Models;
|
|||
using Microsoft.OpenApi.Readers;
|
||||
using Microsoft.OpenApi.Validations;
|
||||
using Microsoft.PowerFx.Connectors.Localization;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Errors;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
|
@ -867,7 +868,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
ExpressionError newError = er is HttpExpressionError her
|
||||
? new HttpExpressionError(her.StatusCode) { Kind = er.Kind, Severity = er.Severity, Message = $"{DPath.Root.Append(new DName(Namespace)).ToDottedSyntax()}.{Name} failed: {er.Message}" }
|
||||
: new ExpressionError() { Kind = er.Kind, Severity = er.Severity, Message = $"{DPath.Root.Append(new DName(Namespace)).ToDottedSyntax()}.{Name} failed: {er.Message}" };
|
||||
result = FormulaValue.NewError(newError, ev.Type);
|
||||
result = FormulaValue.NewError(newError, ev.Type);
|
||||
}
|
||||
|
||||
if (IsPageable && result is RecordValue rv)
|
||||
|
@ -1005,33 +1006,23 @@ namespace Microsoft.PowerFx.Connectors
|
|||
}
|
||||
|
||||
// Only called by ConnectorTable.GetSchema
|
||||
internal static ConnectorType GetConnectorTypeAndTableCapabilities(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue sv, List<SqlRelationship> sqlRelationships, ConnectorCompatibility compatibility, string datasetName, out string name, out string displayName, out ServiceCapabilities tableCapabilities)
|
||||
// Returns a FormulaType with AssociatedDataSources set (done in AddTabularDataSource)
|
||||
internal static ConnectorType GetCdpTableType(ICdpTableResolver tableResolver, string connectorName, string valuePath, StringValue stringValue, List<SqlRelationship> sqlRelationships, ConnectorCompatibility compatibility, string datasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo)
|
||||
{
|
||||
// There are some errors when parsing this Json payload but that's not a problem here as we only need x-ms-capabilities parsing to work
|
||||
OpenApiReaderSettings oars = new OpenApiReaderSettings() { RuleSet = DefaultValidationRuleSet };
|
||||
ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment<OpenApiSchema>(sv.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _));
|
||||
tableCapabilities = tableSchema.GetTableCapabilities();
|
||||
|
||||
JsonElement je = ExtractFromJson(sv, valuePath, out name, out displayName);
|
||||
|
||||
// Json version to be able to read SalesForce unique properties
|
||||
ConnectorType connectorType = GetJsonConnectorTypeInternal(compatibility, je, sqlRelationships);
|
||||
connectorType.Name = name;
|
||||
IList<ReferencedEntity> referencedEntities = GetReferenceEntities(connectorName, sv);
|
||||
ISwaggerSchema tableSchema = SwaggerSchema.New(new OpenApiStringReader(oars).ReadFragment<OpenApiSchema>(stringValue.Value, OpenApi.OpenApiSpecVersion.OpenApi2_0, out OpenApiDiagnostic _));
|
||||
|
||||
ServiceCapabilities serviceCapabilities = tableSchema.GetTableCapabilities();
|
||||
ConnectorPermission tablePermission = tableSchema.GetPermission();
|
||||
bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly;
|
||||
|
||||
List<ConnectorType> primaryKeyParts = connectorType.Fields.Where(f => f.KeyType == ConnectorKeyType.Primary).OrderBy(f => f.KeyOrder).ToList();
|
||||
|
||||
if (primaryKeyParts.Count == 0)
|
||||
{
|
||||
// $$$ need to check what triggers RO for SQL
|
||||
//isTableReadOnly = true;
|
||||
}
|
||||
|
||||
connectorType.AddTabularDataSource(tableResolver, referencedEntities, sqlRelationships, new DName(name), datasetName, connectorType, tableCapabilities, isTableReadOnly);
|
||||
|
||||
|
||||
JsonElement jsonElement = ExtractFromJson(stringValue, valuePath, out name, out displayName);
|
||||
bool isTableReadOnly = tablePermission == ConnectorPermission.PermissionReadOnly;
|
||||
IList<ReferencedEntity> referencedEntities = GetReferenceEntities(connectorName, stringValue);
|
||||
|
||||
ConnectorType connectorType = new ConnectorType(jsonElement, compatibility, sqlRelationships, referencedEntities, datasetName, name, connectorName, tableResolver, serviceCapabilities, isTableReadOnly);
|
||||
delegationInfo = ((DataSourceInfo)connectorType.FormulaType._type.AssociatedDataSources.First()).DelegationInfo;
|
||||
|
||||
return connectorType;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
// Used in TabularIRVisitor
|
||||
internal class CdpDtype : DType
|
||||
{
|
||||
internal TableType TableType;
|
||||
|
||||
internal CdpDtype(TableType tableType)
|
||||
: base(DKind.Table, tableType._type.TypeTree, null, tableType._type.DisplayNameProvider)
|
||||
{
|
||||
TableType = tableType;
|
||||
|
||||
if (tableType._type.AssociatedDataSources != null)
|
||||
{
|
||||
foreach (IExternalTabularDataSource ds in tableType._type.AssociatedDataSources)
|
||||
{
|
||||
AssociatedDataSources.Add(ds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -333,6 +333,8 @@ namespace Microsoft.PowerFx.Connectors
|
|||
internal static bool IsInternal(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.IsInternal() ?? false;
|
||||
|
||||
internal static string GetVisibility(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetVisibility();
|
||||
|
||||
internal static string GetEnumName(this IOpenApiExtensible oae) => SwaggerExtensions.New(oae)?.GetEnumName();
|
||||
|
||||
// Internal parameters are not showen to the user.
|
||||
// They can have a default value or be special cased by the infrastructure (like "connectionId").
|
||||
|
@ -340,6 +342,13 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
internal static string GetVisibility(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsVisibility, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null;
|
||||
|
||||
internal static string GetEnumName(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsEnum, out IOpenApiExtension openApiExt) &&
|
||||
openApiExt is SwaggerJsonObject jsonObject &&
|
||||
jsonObject.TryGetValue("name", out IOpenApiAny enumName) &&
|
||||
enumName is OpenApiString enumNameStr
|
||||
? enumNameStr.Value
|
||||
: null;
|
||||
|
||||
internal static string GetMediaKind(this ISwaggerExtensions schema) => schema.Extensions.TryGetValue(XMsMediaKind, out IOpenApiExtension openApiExt) && openApiExt is OpenApiString openApiStr ? openApiStr.Value : null;
|
||||
|
||||
internal static (bool IsPresent, string Value) GetString(this IDictionary<string, IOpenApiAny> apiObj, string str) => apiObj.TryGetValue(str, out IOpenApiAny openApiAny) && openApiAny is OpenApiString openApiStr ? (true, openApiStr.Value) : (false, null);
|
||||
|
@ -438,7 +447,8 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
if (schema.Enum.All(e => e is OpenApiString))
|
||||
{
|
||||
OptionSet optionSet = new OptionSet("enum", schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary());
|
||||
string enumName = schema.GetEnumName() ?? "enum";
|
||||
OptionSet optionSet = new OptionSet(enumName, schema.Enum.Select(e => new DName((e as OpenApiString).Value)).ToDictionary(k => k, e => e).ToImmutableDictionary());
|
||||
return new ConnectorType(schema, openApiParameter, optionSet.FormulaType);
|
||||
}
|
||||
else
|
||||
|
@ -466,14 +476,19 @@ namespace Microsoft.PowerFx.Connectors
|
|||
case null:
|
||||
case "decimal":
|
||||
case "currency":
|
||||
return new ConnectorType(schema, openApiParameter, FormulaType.Decimal);
|
||||
return new ConnectorType(schema, openApiParameter, FormulaType.Decimal);
|
||||
|
||||
default:
|
||||
return new ConnectorType(error: $"Unsupported type of number: {schema.Format}");
|
||||
}
|
||||
|
||||
// For testing only
|
||||
case "fxnumber":
|
||||
return new ConnectorType(schema, openApiParameter, FormulaType.Number);
|
||||
|
||||
// Always a boolean (Format not used)
|
||||
case "boolean": return new ConnectorType(schema, openApiParameter, FormulaType.Boolean);
|
||||
case "boolean":
|
||||
return new ConnectorType(schema, openApiParameter, FormulaType.Boolean);
|
||||
|
||||
// OpenAPI spec: Format could be <null>, int32, int64
|
||||
case "integer":
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
// Used by ServiceCapabilities.ToDelegationInfo for managing CDP x-ms-capabilities
|
||||
internal class CdpDelegationInfo : TableDelegationInfo
|
||||
{
|
||||
public override ColumnCapabilitiesDefinition GetColumnCapability(string fieldName)
|
||||
{
|
||||
// We should never reach that point in CDP case
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
|
@ -14,19 +14,13 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
internal ConnectorType ConnectorType { get; }
|
||||
|
||||
internal IList<ReferencedEntity> ReferencedEntities { get; }
|
||||
|
||||
internal IList<SqlRelationship> SqlRelationships { get; }
|
||||
|
||||
internal ICdpTableResolver TableResolver { get; }
|
||||
|
||||
internal CdpRecordType(ConnectorType connectorType, DType recordType, ICdpTableResolver tableResolver, IList<ReferencedEntity> referencedEntities, IList<SqlRelationship> sqlRelationships)
|
||||
: base(recordType)
|
||||
internal CdpRecordType(ConnectorType connectorType, ICdpTableResolver tableResolver, TableDelegationInfo delegationInfo)
|
||||
: base(connectorType.DisplayNameProvider, delegationInfo)
|
||||
{
|
||||
ConnectorType = connectorType;
|
||||
TableResolver = tableResolver;
|
||||
ReferencedEntities = referencedEntities;
|
||||
SqlRelationships = sqlRelationships;
|
||||
}
|
||||
|
||||
public bool TryGetFieldExternalTableName(string fieldName, out string tableName, out string foreignKey)
|
||||
|
@ -34,14 +28,9 @@ namespace Microsoft.PowerFx.Connectors
|
|||
tableName = null;
|
||||
foreignKey = null;
|
||||
|
||||
if (!base.TryGetBackingDType(fieldName, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectorType connectorType = ConnectorType.Fields.First(ct => ct.Name == fieldName);
|
||||
|
||||
if (connectorType.ExternalTables?.Any() != true)
|
||||
if (connectorType == null || connectorType.ExternalTables?.Any() != true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -51,28 +40,33 @@ namespace Microsoft.PowerFx.Connectors
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool TryGetFieldType(string fieldName, out FormulaType type)
|
||||
public override bool TryGetUnderlyingFieldType(string name, out FormulaType type) => TryGetFieldType(name, true, out type);
|
||||
|
||||
public override bool TryGetFieldType(string name, out FormulaType type) => TryGetFieldType(name, false, out type);
|
||||
|
||||
private bool TryGetFieldType(string fieldName, bool ignorelationship, out FormulaType type)
|
||||
{
|
||||
if (!base.TryGetBackingDType(fieldName, out _))
|
||||
ConnectorType field = ConnectorType.Fields.FirstOrDefault(ct => ct.Name == fieldName);
|
||||
|
||||
if (field == null)
|
||||
{
|
||||
type = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectorType ct = ConnectorType.Fields.First(ct => ct.Name == fieldName);
|
||||
|
||||
if (ct.ExternalTables?.Any() != true)
|
||||
if (field.ExternalTables?.Any() != true || ignorelationship)
|
||||
{
|
||||
return base.TryGetFieldType(fieldName, out type);
|
||||
type = field.FormulaType;
|
||||
return true;
|
||||
}
|
||||
|
||||
string tableName = ct.ExternalTables.First();
|
||||
string tableName = field.ExternalTables.First();
|
||||
|
||||
try
|
||||
{
|
||||
CdpTableDescriptor ttd = TableResolver.ResolveTableAsync(tableName, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
ConnectorType connectorType = TableResolver.ResolveTableAsync(tableName, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
type = ttd.ConnectorType.FormulaType;
|
||||
type = connectorType.FormulaType;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -84,7 +78,17 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (object.ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other is not CdpRecordType otherRecordType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ConnectorType.Equals(otherRecordType.ConnectorType);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
@ -94,6 +98,6 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public override string TableSymbolName => ConnectorType.Name;
|
||||
|
||||
public override IEnumerable<string> FieldNames => _type.GetRootFieldNames().Select(name => name.Value);
|
||||
public override IEnumerable<string> FieldNames => ConnectorType.Fields.Select(field => field.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
// Used in ConnectorTableValue
|
||||
internal class CdpTableType : TableType
|
||||
{
|
||||
public CdpTableType(TableType tableType)
|
||||
: base(new CdpDtype(tableType))
|
||||
{
|
||||
}
|
||||
|
||||
public override void Visit(ITypeVisitor vistor)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
|
@ -18,22 +18,21 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
public bool IsDelegable => _tabularService.IsDelegable;
|
||||
|
||||
protected internal readonly CdpService _tabularService;
|
||||
protected internal readonly CdpService _tabularService;
|
||||
|
||||
protected internal readonly ConnectorType _connectorType;
|
||||
internal readonly IReadOnlyDictionary<string, Relationship> Relationships;
|
||||
|
||||
private IReadOnlyCollection<DValue<RecordValue>> _cachedRows;
|
||||
|
||||
internal readonly HttpClient HttpClient;
|
||||
|
||||
public RecordType TabularRecordType => _tabularService?.TabularRecordType;
|
||||
public RecordType RecordType => _tabularService?.RecordType;
|
||||
|
||||
public CdpTableValue(CdpService tabularService, ConnectorType connectorType)
|
||||
: base(IRContext.NotInSource(new CdpTableType(tabularService.TableType)))
|
||||
internal CdpTableValue(CdpService tabularService, IReadOnlyDictionary<string, Relationship> relationships)
|
||||
: base(IRContext.NotInSource(tabularService.TableType))
|
||||
{
|
||||
_tabularService = tabularService;
|
||||
_connectorType = connectorType;
|
||||
|
||||
Relationships = relationships;
|
||||
HttpClient = tabularService.HttpClient;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,8 @@ using System.Linq;
|
|||
using System.Text.Json;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.UtilityDataStructures;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using static Microsoft.PowerFx.Connectors.Constants;
|
||||
|
@ -205,6 +203,19 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
}
|
||||
|
||||
internal ConnectorType(JsonElement schema, ConnectorCompatibility compatibility, IList<SqlRelationship> sqlRelationships, IList<ReferencedEntity> referencedEntities, string datasetName, string name, string connectorName, ICdpTableResolver resolver, ServiceCapabilities serviceCapabilities, bool isTableReadOnly)
|
||||
: this(SwaggerJsonSchema.New(schema), null, new SwaggerParameter(null, true, SwaggerJsonSchema.New(schema), null).GetConnectorType(compatibility, sqlRelationships))
|
||||
{
|
||||
Name = name;
|
||||
|
||||
foreach (ConnectorType field in Fields.Where(f => f.Capabilities != null))
|
||||
{
|
||||
serviceCapabilities.AddColumnCapability(field.Name, field.Capabilities);
|
||||
}
|
||||
|
||||
FormulaType = new CdpRecordType(this, resolver, ServiceCapabilities.ToDelegationInfo(serviceCapabilities, name, isTableReadOnly, this, datasetName));
|
||||
}
|
||||
|
||||
internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter, ConnectorType connectorType)
|
||||
: this(schema, openApiParameter, connectorType.FormulaType)
|
||||
{
|
||||
|
@ -263,33 +274,24 @@ namespace Microsoft.PowerFx.Connectors
|
|||
_warnings = connectorType._warnings;
|
||||
}
|
||||
|
||||
internal DisplayNameProvider DisplayNameProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
_displayNameProvider ??= new SingleSourceDisplayNameProvider(Fields.Select(field => new KeyValuePair<DName, DName>(new DName(field.Name), new DName(field.DisplayName ?? field.Name))));
|
||||
return _displayNameProvider;
|
||||
}
|
||||
}
|
||||
|
||||
private DisplayNameProvider _displayNameProvider;
|
||||
|
||||
internal void SetRelationship(SqlRelationship relationship)
|
||||
{
|
||||
ExternalTables ??= new List<string>();
|
||||
ExternalTables.Add(relationship.ReferencedTable);
|
||||
RelationshipName = relationship.RelationshipName;
|
||||
ForeignKey = relationship.ReferencedColumnName;
|
||||
}
|
||||
|
||||
internal void AddTabularDataSource(ICdpTableResolver tableResolver, IList<ReferencedEntity> referencedEntities, List<SqlRelationship> sqlRelationships, DName name, string datasetName, ConnectorType connectorType, ServiceCapabilities serviceCapabilities, bool isReadOnly, BidirectionalDictionary<string, string> displayNameMapping = null)
|
||||
{
|
||||
if (FormulaType is not RecordType)
|
||||
{
|
||||
throw new PowerFxConnectorException("Invalid FormulaType");
|
||||
}
|
||||
|
||||
// $$$ Hack to enable IExternalTabularDataSource, will be removed later
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (tableResolver.GenerateADS)
|
||||
{
|
||||
HashSet<IExternalTabularDataSource> dataSource = new HashSet<IExternalTabularDataSource>() { new ExternalCdpDataSource(name, datasetName, serviceCapabilities, isReadOnly, displayNameMapping) };
|
||||
DType newDType = DType.CreateDTypeWithConnectedDataSourceInfoMetadata(FormulaType._type, dataSource, null);
|
||||
FormulaType = new KnownRecordType(newDType);
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
FormulaType = new CdpRecordType(connectorType, FormulaType._type, tableResolver, referencedEntities, sqlRelationships);
|
||||
}
|
||||
}
|
||||
|
||||
private void AggregateErrors(ConnectorType[] types)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
public const string FilterRequiredProperties = "requiredProperties";
|
||||
public const string FilterRestrictions = "filterRestrictions";
|
||||
public const string GroupRestriction = "groupRestriction";
|
||||
public const string IsChoiceValue = "Value";
|
||||
public const string IsDelegable = "isDelegable";
|
||||
public const string IsOnlyServerPagable = "isOnlyServerPagable";
|
||||
public const string IsPageable = "isPageable";
|
||||
|
@ -36,6 +37,6 @@ namespace Microsoft.PowerFx.Connectors
|
|||
public const string SPQueryName = "OdataQueryName";
|
||||
public const string SupportsRecordPermission = "supportsRecordPermission";
|
||||
public const string UngroupableProperties = "ungroupableProperties";
|
||||
public const string UnsortableProperties = "unsortableProperties";
|
||||
public const string UnsortableProperties = "unsortableProperties";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,22 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public Dictionary<string, ColumnCapabilitiesBase> Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null;
|
||||
|
||||
private readonly Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
|
||||
private Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(Constants.XMsCapabilities)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly ColumnCapabilitiesDefinition Capabilities;
|
||||
public ColumnCapabilitiesDefinition Capabilities;
|
||||
|
||||
public static ColumnCapabilities DefaultColumnCapabilities => new ColumnCapabilities()
|
||||
{
|
||||
Capabilities = new ColumnCapabilitiesDefinition(null),
|
||||
_childColumnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>()
|
||||
};
|
||||
|
||||
private ColumnCapabilities()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
|
||||
{
|
||||
|
@ -56,7 +66,11 @@ namespace Microsoft.PowerFx.Connectors
|
|||
return null;
|
||||
}
|
||||
|
||||
return new ColumnCapabilities(new ColumnCapabilitiesDefinition(filterFunctions, propertyAlias, isChoice));
|
||||
return new ColumnCapabilities(new ColumnCapabilitiesDefinition(filterFunctions)
|
||||
{
|
||||
QueryAlias = propertyAlias,
|
||||
IsChoice = isChoice
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
||||
|
@ -14,26 +15,23 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.FilterFunctions)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly string[] FilterFunctions;
|
||||
public IEnumerable<string> FilterFunctions { get; }
|
||||
|
||||
// used in PowerApps-Client/src/AppMagic/js/Core/Core.Data/ConnectedDataDeserialization/TabularDataDeserialization.ts
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.PropertyQueryAlias)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly string QueryAlias;
|
||||
internal string QueryAlias { get; init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.SPIsChoice)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public readonly bool? IsChoice;
|
||||
|
||||
public ColumnCapabilitiesDefinition(string[] filterFunction, string alias, bool? isChoice)
|
||||
{
|
||||
Contracts.AssertValueOrNull(filterFunction);
|
||||
Contracts.AssertValueOrNull(alias);
|
||||
|
||||
FilterFunctions = filterFunction;
|
||||
QueryAlias = alias;
|
||||
IsChoice = isChoice;
|
||||
internal bool? IsChoice { get; init; }
|
||||
|
||||
public ColumnCapabilitiesDefinition(IEnumerable<string> filterFunction)
|
||||
{
|
||||
// ex: lt, le, eq, ne, gt, ge, and, or, not, contains, startswith, endswith, countdistinct, day, month, year, time
|
||||
FilterFunctions = filterFunction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,19 +15,22 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.FilterRequiredProperties)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly List<string> RequiredProperties;
|
||||
public readonly IList<string> RequiredProperties;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.NonFilterableProperties)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly List<string> NonFilterableProperties;
|
||||
public readonly IList<string> NonFilterableProperties;
|
||||
|
||||
public FilterRestriction(List<string> requiredProperties, List<string> nonFilterableProperties)
|
||||
public FilterRestriction(IList<string> requiredProperties, IList<string> nonFilterableProperties)
|
||||
{
|
||||
Contracts.AssertValueOrNull(requiredProperties);
|
||||
Contracts.AssertValueOrNull(nonFilterableProperties);
|
||||
|
||||
// List of required properties
|
||||
RequiredProperties = requiredProperties;
|
||||
|
||||
// List of non filterable properties
|
||||
NonFilterableProperties = nonFilterableProperties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.UngroupableProperties)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly List<string> UngroupableProperties;
|
||||
public readonly IList<string> UngroupableProperties;
|
||||
|
||||
public GroupRestriction(List<string> ungroupableProperties)
|
||||
public GroupRestriction(IList<string> ungroupableProperties)
|
||||
{
|
||||
Contracts.AssertValueOrNull(ungroupableProperties);
|
||||
|
||||
|
|
|
@ -1,17 +1,27 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
internal sealed class PagingCapabilities
|
||||
{
|
||||
public readonly bool IsOnlyServerPagable;
|
||||
|
||||
public readonly string[] ServerPagingOptions;
|
||||
public readonly IEnumerable<string> ServerPagingOptions;
|
||||
|
||||
public PagingCapabilities(bool isOnlyServerPagable, string[] serverPagingOptions)
|
||||
{
|
||||
// Server paging restrictions, true for CDS
|
||||
// Setting 'IsOnlyServerPagable' to true in the table metadata response lets PowerApps application to use
|
||||
// @odata.nextlink URL in reponse message (instead of $skip and $top query parameters) for page traversal.
|
||||
// It is also required to set sortable and filterable restrictions for PowerApps to page through results.
|
||||
IsOnlyServerPagable = isOnlyServerPagable;
|
||||
|
||||
// List of supported server-driven paging capabilities, null for CDS
|
||||
// ex: top, skiptoken
|
||||
// used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/AppMagic/js/AppMagic.Services/ConnectedData/CdpConnector.ts&_a=contents&version=GBmaster
|
||||
ServerPagingOptions = serverPagingOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public SelectionRestriction(bool isSelectable)
|
||||
{
|
||||
// Indicates whether this table has selectable columns
|
||||
// Used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/Cloud/DocumentServer.Core/Document/Document/InfoTypes/CdsDataSourceInfo.cs&_a=contents&version=GBmaster
|
||||
IsSelectable = isSelectable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
||||
// DO NOT INCLUDE Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata ASSEMBLY
|
||||
|
@ -52,17 +56,21 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.FilterFunctions)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly string[] FilterFunctions;
|
||||
public readonly IEnumerable<string> FilterFunctions;
|
||||
|
||||
public IEnumerable<DelegationOperator> FilterFunctionsEnum => GetDelegationOperatorEnumList(FilterFunctions);
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.FilterFunctionSupport)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly string[] FilterSupportedFunctions;
|
||||
public readonly IEnumerable<string> FilterSupportedFunctions;
|
||||
|
||||
public IEnumerable<DelegationOperator> FilterSupportedFunctionsEnum => GetDelegationOperatorEnumList(FilterSupportedFunctions);
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.ServerPagingOptions)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public string[] ServerPagingOptions => PagingCapabilities.ServerPagingOptions;
|
||||
public IEnumerable<string> ServerPagingOptions => PagingCapabilities.ServerPagingOptions;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.IsOnlyServerPagable)]
|
||||
|
@ -95,8 +103,8 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public const int CurrentODataVersion = 4;
|
||||
|
||||
public ServiceCapabilities(SortRestriction sortRestriction, FilterRestriction filterRestriction, SelectionRestriction selectionRestriction, GroupRestriction groupRestriction, string[] filterFunctions, string[] filterSupportedFunctions,
|
||||
PagingCapabilities pagingCapabilities, bool recordPermissionCapabilities, int oDataVersion = CurrentODataVersion, bool supportsDataverseOffline = false)
|
||||
public ServiceCapabilities(SortRestriction sortRestriction, FilterRestriction filterRestriction, SelectionRestriction selectionRestriction, GroupRestriction groupRestriction, IEnumerable<string> filterFunctions,
|
||||
IEnumerable<string> filterSupportedFunctions, PagingCapabilities pagingCapabilities, bool recordPermissionCapabilities, int oDataVersion = CurrentODataVersion, bool supportsDataverseOffline = false)
|
||||
{
|
||||
Contracts.AssertValueOrNull(sortRestriction);
|
||||
Contracts.AssertValueOrNull(filterRestriction);
|
||||
|
@ -122,16 +130,96 @@ namespace Microsoft.PowerFx.Connectors
|
|||
SupportsRecordPermission = recordPermissionCapabilities;
|
||||
}
|
||||
|
||||
public static TableDelegationInfo ToDelegationInfo(ServiceCapabilities serviceCapabilities, string tableName, bool isReadOnly, ConnectorType connectorType, string datasetName)
|
||||
{
|
||||
SortRestrictions sortRestriction = new SortRestrictions()
|
||||
{
|
||||
AscendingOnlyProperties = serviceCapabilities?.SortRestriction?.AscendingOnlyProperties,
|
||||
UnsortableProperties = serviceCapabilities?.SortRestriction?.UnsortableProperties
|
||||
};
|
||||
|
||||
FilterRestrictions filterRestriction = new FilterRestrictions()
|
||||
{
|
||||
RequiredProperties = serviceCapabilities?.FilterRestriction?.RequiredProperties,
|
||||
NonFilterableProperties = serviceCapabilities?.FilterRestriction?.NonFilterableProperties
|
||||
};
|
||||
|
||||
SelectionRestrictions selectionRestriction = new SelectionRestrictions()
|
||||
{
|
||||
IsSelectable = serviceCapabilities?.SelectionRestriction?.IsSelectable ?? false
|
||||
};
|
||||
|
||||
GroupRestrictions groupRestriction = new GroupRestrictions()
|
||||
{
|
||||
UngroupableProperties = serviceCapabilities?.GroupRestriction?.UngroupableProperties
|
||||
};
|
||||
|
||||
Core.Entities.PagingCapabilities pagingCapabilities = new Core.Entities.PagingCapabilities()
|
||||
{
|
||||
IsOnlyServerPagable = serviceCapabilities?.PagingCapabilities?.IsOnlyServerPagable ?? false,
|
||||
ServerPagingOptions = serviceCapabilities?.PagingCapabilities?.ServerPagingOptions?.ToArray()
|
||||
};
|
||||
|
||||
Dictionary<string, Core.Entities.ColumnCapabilitiesBase> columnCapabilities = serviceCapabilities?._columnsCapabilities?.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value switch
|
||||
{
|
||||
ColumnCapabilities cc => new Core.Entities.ColumnCapabilities(new Core.Entities.ColumnCapabilitiesDefinition()
|
||||
{
|
||||
FilterFunctions = GetDelegationOperatorEnumList(cc.Capabilities.FilterFunctions),
|
||||
QueryAlias = cc.Capabilities.QueryAlias,
|
||||
IsChoice = cc.Capabilities.IsChoice
|
||||
}) as Core.Entities.ColumnCapabilitiesBase,
|
||||
ComplexColumnCapabilities ccc => new Core.Entities.ComplexColumnCapabilities() as Core.Entities.ColumnCapabilitiesBase,
|
||||
_ => throw new NotImplementedException()
|
||||
});
|
||||
|
||||
Dictionary<string, string> columnWithRelationships = connectorType.Fields.Where(f => f.ExternalTables?.Any() == true).Select(f => (f.Name, f.ExternalTables.First())).ToDictionary(tpl => tpl.Name, tpl => tpl.Item2);
|
||||
|
||||
return new CdpDelegationInfo()
|
||||
{
|
||||
TableName = tableName,
|
||||
IsReadOnly = isReadOnly,
|
||||
DatasetName = datasetName,
|
||||
SortRestriction = sortRestriction,
|
||||
FilterRestriction = filterRestriction,
|
||||
SelectionRestriction = selectionRestriction,
|
||||
GroupRestriction = groupRestriction,
|
||||
FilterFunctions = serviceCapabilities?.FilterFunctionsEnum,
|
||||
FilterSupportedFunctions = serviceCapabilities?.FilterSupportedFunctionsEnum,
|
||||
PagingCapabilities = pagingCapabilities,
|
||||
SupportsRecordPermission = serviceCapabilities?.SupportsRecordPermission ?? false,
|
||||
ColumnsCapabilities = columnCapabilities,
|
||||
ColumnsWithRelationships = columnWithRelationships
|
||||
};
|
||||
}
|
||||
|
||||
private static IEnumerable<DelegationOperator> GetDelegationOperatorEnumList(IEnumerable<string> filterFunctionList)
|
||||
{
|
||||
if (filterFunctionList == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<DelegationOperator> list = new List<DelegationOperator>();
|
||||
|
||||
foreach (string str in filterFunctionList)
|
||||
{
|
||||
if (Enum.TryParse(str, true, out DelegationOperator op))
|
||||
{
|
||||
list.Add(op);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
|
||||
{
|
||||
Contracts.AssertNonEmpty(name);
|
||||
Contracts.AssertValue(capability);
|
||||
|
||||
if (_columnsCapabilities == null)
|
||||
{
|
||||
_columnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>();
|
||||
}
|
||||
|
||||
_columnsCapabilities ??= new Dictionary<string, ColumnCapabilitiesBase>();
|
||||
_columnsCapabilities.Add(name, capability);
|
||||
}
|
||||
|
||||
|
@ -160,32 +248,32 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
IDictionary<string, IOpenApiAny> filterRestritionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.FilterRestrictions);
|
||||
return filterRestritionMetaData?.GetBool(CapabilityConstants.Filterable) == true
|
||||
? new FilterRestriction(filterRestritionMetaData.GetList(CapabilityConstants.FilterRequiredProperties), filterRestritionMetaData.GetList(CapabilityConstants.NonFilterableProperties))
|
||||
: null;
|
||||
? new FilterRestriction(filterRestritionMetaData.GetList(CapabilityConstants.FilterRequiredProperties), filterRestritionMetaData.GetList(CapabilityConstants.NonFilterableProperties))
|
||||
: null;
|
||||
}
|
||||
|
||||
private static SortRestriction ParseSortRestriction(IDictionary<string, IOpenApiAny> capabilitiesMetaData)
|
||||
{
|
||||
IDictionary<string, IOpenApiAny> sortRestrictionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.SortRestrictions);
|
||||
return sortRestrictionMetaData?.GetBool(CapabilityConstants.Sortable) == true
|
||||
? new SortRestriction(sortRestrictionMetaData.GetList(CapabilityConstants.UnsortableProperties), sortRestrictionMetaData.GetList(CapabilityConstants.AscendingOnlyProperties))
|
||||
: null;
|
||||
? new SortRestriction(sortRestrictionMetaData.GetList(CapabilityConstants.UnsortableProperties), sortRestrictionMetaData.GetList(CapabilityConstants.AscendingOnlyProperties))
|
||||
: null;
|
||||
}
|
||||
|
||||
private static SelectionRestriction ParseSelectionRestriction(IDictionary<string, IOpenApiAny> capabilitiesMetaData)
|
||||
{
|
||||
IDictionary<string, IOpenApiAny> selectRestrictionsMetadata = capabilitiesMetaData.GetObject(CapabilityConstants.SelectionRestriction);
|
||||
return selectRestrictionsMetadata == null
|
||||
? null
|
||||
: new SelectionRestriction(selectRestrictionsMetadata.GetBool(CapabilityConstants.Selectable, "selectable property is mandatory and not found."));
|
||||
? null
|
||||
: new SelectionRestriction(selectRestrictionsMetadata.GetBool(CapabilityConstants.Selectable, "selectable property is mandatory and not found."));
|
||||
}
|
||||
|
||||
private static GroupRestriction ParseGroupRestriction(IDictionary<string, IOpenApiAny> capabilitiesMetaData)
|
||||
{
|
||||
IDictionary<string, IOpenApiAny> groupRestrictionMetaData = capabilitiesMetaData.GetObject(CapabilityConstants.GroupRestriction);
|
||||
return groupRestrictionMetaData == null
|
||||
? null
|
||||
: new GroupRestriction(groupRestrictionMetaData.GetList(CapabilityConstants.UngroupableProperties));
|
||||
? null
|
||||
: new GroupRestriction(groupRestrictionMetaData.GetList(CapabilityConstants.UngroupableProperties));
|
||||
}
|
||||
|
||||
internal static string[] ParseFilterFunctions(IDictionary<string, IOpenApiAny> capabilitiesMetaData)
|
||||
|
|
|
@ -15,19 +15,22 @@ namespace Microsoft.PowerFx.Connectors
|
|||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.AscendingOnlyProperties)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly List<string> AscendingOnlyProperties;
|
||||
public readonly IList<string> AscendingOnlyProperties;
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName(CapabilityConstants.UnsortableProperties)]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public readonly List<string> UnsortableProperties;
|
||||
public readonly IList<string> UnsortableProperties;
|
||||
|
||||
public SortRestriction(List<string> unsortableProperties, List<string> ascendingOnlyProperties)
|
||||
public SortRestriction(IList<string> unsortableProperties, IList<string> ascendingOnlyProperties)
|
||||
{
|
||||
Contracts.AssertValueOrNull(unsortableProperties);
|
||||
Contracts.AssertValueOrNull(ascendingOnlyProperties);
|
||||
|
||||
// List of properties which support ascending order only
|
||||
AscendingOnlyProperties = ascendingOnlyProperties;
|
||||
|
||||
// List of unsortable properties
|
||||
UnsortableProperties = unsortableProperties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
internal readonly struct CdpTableDescriptor
|
||||
{
|
||||
public ConnectorType ConnectorType { get; init; }
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public string DisplayName { get; init; }
|
||||
|
||||
public ServiceCapabilities TableCapabilities { get; init; }
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using System.Net.Http;
|
|||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
|
@ -24,9 +25,6 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
private readonly bool _doubleEncoding;
|
||||
|
||||
// Temporary hack to generate ADS
|
||||
public bool GenerateADS { get; init; }
|
||||
|
||||
public CdpTableResolver(CdpTable tabularTable, HttpClient httpClient, string uriPrefix, bool doubleEncoding, ConnectorLogger logger = null)
|
||||
{
|
||||
_tabularTable = tabularTable;
|
||||
|
@ -37,7 +35,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task<CdpTableDescriptor> ResolveTableAsync(string tableName, CancellationToken cancellationToken)
|
||||
public async Task<ConnectorType> ResolveTableAsync(string tableName, CancellationToken cancellationToken)
|
||||
{
|
||||
// out string name, out string displayName, out ServiceCapabilities tableCapabilities
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
@ -56,41 +54,40 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
string text = await CdpServiceBase.GetObject(_httpClient, $"Get table metadata", uri, null, cancellationToken, Logger).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
List<SqlRelationship> sqlRelationships = null;
|
||||
|
||||
// for SQL need to get relationships separately as they aren't included by CDP connector
|
||||
if (IsSql())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
uri = (_uriPrefix ?? string.Empty) + $"/v2/datasets/{dataset}/query/sql";
|
||||
string body =
|
||||
@"{""query"":""SELECT fk.name AS FK_Name, '[' + sp.name + '].[' + tp.name + ']' AS Parent_Table, cp.name AS Parent_Column, '[' + sr.name + '].[' + tr.name + ']' AS Referenced_Table, cr.name AS Referenced_Column" +
|
||||
@" FROM sys.foreign_keys fk" +
|
||||
@" INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id" +
|
||||
@" INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id" +
|
||||
@" INNER JOIN sys.schemas sp on tp.schema_id = sp.schema_id" +
|
||||
@" INNER JOIN sys.schemas sr on tr.schema_id = sr.schema_id" +
|
||||
@" INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id" +
|
||||
@" INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id" +
|
||||
@" INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id" +
|
||||
@" WHERE '[' + sp.name + '].[' + tp.name + ']' = '" + tableName + "'" + @"""}";
|
||||
|
||||
string text2 = await CdpServiceBase.GetObject(_httpClient, $"Get SQL relationships", uri, body, cancellationToken, Logger).ConfigureAwait(false);
|
||||
|
||||
// Result should be cached
|
||||
sqlRelationships = GetSqlRelationships(text2);
|
||||
}
|
||||
|
||||
string connectorName = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1];
|
||||
ConnectorType ct = ConnectorFunction.GetConnectorTypeAndTableCapabilities(this, connectorName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, out string name, out string displayName, out ServiceCapabilities tableCapabilities);
|
||||
|
||||
return new CdpTableDescriptor() { ConnectorType = ct, Name = name, DisplayName = displayName, TableCapabilities = tableCapabilities };
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CdpTableDescriptor();
|
||||
List<SqlRelationship> sqlRelationships = null;
|
||||
|
||||
// for SQL need to get relationships separately as they aren't included by CDP connector
|
||||
if (IsSql())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
uri = (_uriPrefix ?? string.Empty) + $"/v2/datasets/{dataset}/query/sql";
|
||||
string body =
|
||||
@"{""query"":""SELECT fk.name AS FK_Name, '[' + sp.name + '].[' + tp.name + ']' AS Parent_Table, cp.name AS Parent_Column, '[' + sr.name + '].[' + tr.name + ']' AS Referenced_Table, cr.name AS Referenced_Column" +
|
||||
@" FROM sys.foreign_keys fk" +
|
||||
@" INNER JOIN sys.tables tp ON fk.parent_object_id = tp.object_id" +
|
||||
@" INNER JOIN sys.tables tr ON fk.referenced_object_id = tr.object_id" +
|
||||
@" INNER JOIN sys.schemas sp on tp.schema_id = sp.schema_id" +
|
||||
@" INNER JOIN sys.schemas sr on tr.schema_id = sr.schema_id" +
|
||||
@" INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id" +
|
||||
@" INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id" +
|
||||
@" INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id" +
|
||||
@" WHERE '[' + sp.name + '].[' + tp.name + ']' = '" + tableName + "'" + @"""}";
|
||||
|
||||
string text2 = await CdpServiceBase.GetObject(_httpClient, $"Get SQL relationships", uri, body, cancellationToken, Logger).ConfigureAwait(false);
|
||||
|
||||
// Result should be cached
|
||||
sqlRelationships = GetSqlRelationships(text2);
|
||||
}
|
||||
|
||||
string connectorName = _uriPrefix.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)[1];
|
||||
|
||||
return ConnectorFunction.GetCdpTableType(this, connectorName, "Schema/Items", FormulaValue.New(text), sqlRelationships, ConnectorCompatibility.CdpCompatibility, _tabularTable.DatasetName, out string name, out string displayName, out TableDelegationInfo delegationInfo);
|
||||
}
|
||||
|
||||
private bool IsSql() => _uriPrefix.Contains("/sql/");
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Entities.Delegation;
|
||||
using Microsoft.PowerFx.Core.Entities.QueryOptions;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.UtilityDataStructures;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
internal class ExternalCdpDataSource : IExternalTabularDataSource
|
||||
{
|
||||
public ExternalCdpDataSource(DName name, string datasetName, ServiceCapabilities serviceCapabilities, bool isReadOnly, BidirectionalDictionary<string, string> displayNameMapping = null)
|
||||
{
|
||||
EntityName = name;
|
||||
ServiceCapabilities = serviceCapabilities;
|
||||
IsWritable = !isReadOnly;
|
||||
|
||||
CdpEntityMetadataProvider metadataProvider = new CdpEntityMetadataProvider();
|
||||
CdpDataSourceMetadata tabularDataSourceMetadata = new CdpDataSourceMetadata(name.Value, datasetName);
|
||||
tabularDataSourceMetadata.LoadClientSemantics();
|
||||
metadataProvider.AddSource(name.Value, tabularDataSourceMetadata);
|
||||
|
||||
DataEntityMetadataProvider = metadataProvider;
|
||||
IsConvertingDisplayNameMapping = false;
|
||||
DisplayNameMapping = displayNameMapping ?? new BidirectionalDictionary<string, string>();
|
||||
PreviousDisplayNameMapping = null;
|
||||
}
|
||||
|
||||
internal ServiceCapabilities ServiceCapabilities;
|
||||
|
||||
public TabularDataQueryOptions QueryOptions => new TabularDataQueryOptions(this);
|
||||
|
||||
public bool HasCachedCountRows => false;
|
||||
|
||||
public string Name => EntityName.Value;
|
||||
|
||||
public bool IsSelectable => ServiceCapabilities.IsSelectable;
|
||||
|
||||
public bool IsDelegatable => ServiceCapabilities.IsDelegable;
|
||||
|
||||
public bool IsPageable => ServiceCapabilities.IsPagable;
|
||||
|
||||
public bool IsClearable => throw new NotImplementedException();
|
||||
|
||||
public bool IsRefreshable => true;
|
||||
|
||||
public bool RequiresAsync => true;
|
||||
|
||||
public bool IsWritable { get; }
|
||||
|
||||
public IExternalDataEntityMetadataProvider DataEntityMetadataProvider { get; }
|
||||
|
||||
public DataSourceKind Kind => DataSourceKind.Connected;
|
||||
|
||||
public IExternalTableMetadata TableMetadata => null; /* _tableMetadata; */
|
||||
|
||||
public IDelegationMetadata DelegationMetadata => throw new NotImplementedException();
|
||||
|
||||
public DName EntityName { get; }
|
||||
|
||||
public DType Type => throw new NotImplementedException();
|
||||
|
||||
public bool IsConvertingDisplayNameMapping { get; protected set; }
|
||||
|
||||
public BidirectionalDictionary<string, string> DisplayNameMapping { get; protected set; }
|
||||
|
||||
public BidirectionalDictionary<string, string> PreviousDisplayNameMapping { get; protected set; }
|
||||
|
||||
public bool CanIncludeExpand(IExpandInfo expandToAdd) => true;
|
||||
|
||||
public bool CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd) => true;
|
||||
|
||||
public bool CanIncludeSelect(string selectColumnName) => TableMetadata != null; /* && TableMetadata.CanIncludeSelect(selectColumnName);*/
|
||||
|
||||
public bool CanIncludeSelect(IExpandInfo expandInfo, string selectColumnName) => true;
|
||||
|
||||
public IReadOnlyList<string> GetKeyColumns()
|
||||
{
|
||||
return /*TableMetadata?.KeyColumns ??*/ new List<string>();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetKeyColumns(IExpandInfo expandInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -11,9 +10,6 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
ConnectorLogger Logger { get; }
|
||||
|
||||
[Obsolete("This property is a temporary hack to generate ADS")]
|
||||
bool GenerateADS { get; init; }
|
||||
|
||||
Task<CdpTableDescriptor> ResolveTableAsync(string tableName, CancellationToken cancellationToken);
|
||||
Task<ConnectorType> ResolveTableAsync(string tableName, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
return await GetObject<DatasetMetadata>(httpClient, "Get datasets metadata", uri, null, cancellationToken, logger).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CdpTable>> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
public virtual async Task<IEnumerable<CdpTable>> GetTablesAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
if (DatasetMetadata == null)
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
+ (uriPrefix.Contains("/sharepointonline/") ? "/alltables" : "/tables");
|
||||
|
||||
GetTables tables = await GetObject<GetTables>(httpClient, "Get tables", uri, null, cancellationToken, logger).ConfigureAwait(false);
|
||||
return tables?.Value?.Select(rt => new CdpTable(DatasetName, rt.Name, DatasetMetadata, tables?.Value) { DisplayName = rt.DisplayName });
|
||||
return tables?.Value?.Select((RawTable rawTable) => new CdpTable(DatasetName, rawTable.Name, DatasetMetadata, tables?.Value) { DisplayName = rawTable.DisplayName });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -61,7 +61,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
/// <param name="logger">Logger.</param>
|
||||
/// <returns>CdpTable class.</returns>
|
||||
/// <exception cref="InvalidOperationException">When no or more than one tables are identified.</exception>
|
||||
public async Task<CdpTable> GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
public virtual async Task<CdpTable> GetTableAsync(HttpClient httpClient, string uriPrefix, string tableName, bool? logicalOrDisplay, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
IEnumerable<CdpTable> tables = await GetTablesAsync(httpClient, uriPrefix, cancellationToken, logger).ConfigureAwait(false);
|
||||
|
|
|
@ -14,30 +14,25 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
private const string NotInitialized = "Tabular service is not initialized.";
|
||||
|
||||
public TableType TableType => TabularRecordType?.ToTable();
|
||||
public TableType TableType => RecordType?.ToTable();
|
||||
|
||||
public RecordType TabularRecordType { get; private set; } = null;
|
||||
public RecordType RecordType { get; protected internal set; } = null;
|
||||
|
||||
public bool IsInitialized => TableType != null;
|
||||
|
||||
public abstract bool IsDelegable { get; }
|
||||
|
||||
public abstract ConnectorType ConnectorType { get; }
|
||||
internal abstract IReadOnlyDictionary<string, Relationship> Relationships { get; }
|
||||
|
||||
public abstract HttpClient HttpClient { get; }
|
||||
|
||||
public virtual CdpTableValue GetTableValue()
|
||||
{
|
||||
return IsInitialized
|
||||
? new CdpTableValue(this, ConnectorType)
|
||||
? new CdpTableValue(this, Relationships)
|
||||
: throw new InvalidOperationException(NotInitialized);
|
||||
}
|
||||
|
||||
protected void SetRecordType(RecordType recordType)
|
||||
{
|
||||
TabularRecordType = recordType;
|
||||
}
|
||||
|
||||
// TABLE METADATA SERVICE
|
||||
// GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01
|
||||
// Implemented in InitAsync() in derived classes
|
||||
|
|
|
@ -8,13 +8,14 @@ using System.Net.Http;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
{
|
||||
// Implements CDP protocol for Tabular connectors
|
||||
public sealed class CdpTable : CdpService
|
||||
public class CdpTable : CdpService
|
||||
{
|
||||
public string TableName { get; private set; }
|
||||
|
||||
|
@ -24,15 +25,15 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public override HttpClient HttpClient => _httpClient;
|
||||
|
||||
public override bool IsDelegable => TableCapabilities?.IsDelegable ?? false;
|
||||
public override bool IsDelegable => (DelegationInfo?.SortRestriction != null) || (DelegationInfo?.FilterRestriction != null) || (DelegationInfo?.FilterFunctions != null);
|
||||
|
||||
public override ConnectorType ConnectorType => TabularTableDescriptor.ConnectorType;
|
||||
internal TableDelegationInfo DelegationInfo => ((DataSourceInfo)TabularTableDescriptor.FormulaType._type.AssociatedDataSources.First()).DelegationInfo;
|
||||
|
||||
internal ServiceCapabilities TableCapabilities => TabularTableDescriptor.TableCapabilities;
|
||||
internal override IReadOnlyDictionary<string, Relationship> Relationships => _relationships;
|
||||
|
||||
internal DatasetMetadata DatasetMetadata;
|
||||
|
||||
internal CdpTableDescriptor TabularTableDescriptor;
|
||||
internal ConnectorType TabularTableDescriptor;
|
||||
|
||||
internal IReadOnlyCollection<RawTable> Tables;
|
||||
|
||||
|
@ -40,6 +41,8 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
private HttpClient _httpClient;
|
||||
|
||||
private IReadOnlyDictionary<string, Relationship> _relationships;
|
||||
|
||||
internal CdpTable(string dataset, string table, IReadOnlyCollection<RawTable> tables)
|
||||
{
|
||||
DatasetName = dataset ?? throw new ArgumentNullException(nameof(dataset));
|
||||
|
@ -55,7 +58,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
//// TABLE METADATA SERVICE
|
||||
// GET: /$metadata.json/datasets/{datasetName}/tables/{tableName}?api-version=2015-09-01
|
||||
public async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
public virtual async Task InitAsync(HttpClient httpClient, string uriPrefix, CancellationToken cancellationToken, ConnectorLogger logger = null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
|
@ -66,14 +69,6 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
_httpClient = httpClient;
|
||||
|
||||
// $$$ This is a hack to generate ADS
|
||||
bool adsHack = false;
|
||||
if (uriPrefix.StartsWith("*", StringComparison.Ordinal))
|
||||
{
|
||||
adsHack = true;
|
||||
uriPrefix = uriPrefix.Substring(1);
|
||||
}
|
||||
|
||||
if (DatasetMetadata == null)
|
||||
{
|
||||
await InitializeDatasetMetadata(httpClient, uriPrefix, logger, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -81,10 +76,12 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
_uriPrefix = uriPrefix;
|
||||
|
||||
CdpTableResolver tableResolver = new CdpTableResolver(this, httpClient, uriPrefix, DatasetMetadata.IsDoubleEncoding, logger) { GenerateADS = adsHack };
|
||||
CdpTableResolver tableResolver = new CdpTableResolver(this, httpClient, uriPrefix, DatasetMetadata.IsDoubleEncoding, logger);
|
||||
TabularTableDescriptor = await tableResolver.ResolveTableAsync(TableName, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
SetRecordType((RecordType)TabularTableDescriptor.ConnectorType?.FormulaType);
|
||||
_relationships = TabularTableDescriptor.Relationships;
|
||||
|
||||
RecordType = (RecordType)TabularTableDescriptor.FormulaType;
|
||||
}
|
||||
|
||||
private async Task InitializeDatasetMetadata(HttpClient httpClient, string uriPrefix, ConnectorLogger logger, CancellationToken cancellationToken)
|
||||
|
|
209
src/libraries/Microsoft.PowerFx.Core/Entities/External/DataSourceInfo.cs
поставляемый
Normal file
209
src/libraries/Microsoft.PowerFx.Core/Entities/External/DataSourceInfo.cs
поставляемый
Normal file
|
@ -0,0 +1,209 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerFx.Core.Entities.Delegation;
|
||||
using Microsoft.PowerFx.Core.Entities.QueryOptions;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.UtilityDataStructures;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
// Implements a base data source, used in DType.AssociatedDataSources, itself in RecordType constructor to host a CDP record type
|
||||
internal class DataSourceInfo : IExternalTabularDataSource
|
||||
{
|
||||
// Key = field logical name, Value = foreign table logical name
|
||||
public readonly IReadOnlyDictionary<string, string> ColumnsWithRelationships;
|
||||
|
||||
public DataSourceInfo(AggregateType recordType, DisplayNameProvider displayNameProvider, TableDelegationInfo delegationInfo)
|
||||
{
|
||||
EntityName = new DName(delegationInfo.TableName);
|
||||
IsWritable = !delegationInfo.IsReadOnly;
|
||||
_delegationInfo = delegationInfo;
|
||||
_type = recordType._type;
|
||||
RecordType = (RecordType)recordType;
|
||||
|
||||
_displayNameMapping = new BidirectionalDictionary<string, string>(displayNameProvider.LogicalToDisplayPairs.ToDictionary(kvp => kvp.Key.Value, kvp => kvp.Value.Value ?? kvp.Key.Value));
|
||||
_externalDataEntityMetadataProvider = new InternalDataEntityMetadataProvider();
|
||||
_externalDataEntityMetadataProvider.AddSource(Name, new InternalDataEntityMetadata(delegationInfo.TableName, delegationInfo.DatasetName, _displayNameMapping));
|
||||
_externalTableMetadata = new InternalTableMetadata(RecordType, Name, Name, delegationInfo.IsReadOnly);
|
||||
_delegationMetadata = new DelegationMetadataBase(_type, new CompositeCapabilityMetadata(_type, GetCapabilityMetadata(recordType, delegationInfo)));
|
||||
_tabularDataQueryOptions = new TabularDataQueryOptions(this);
|
||||
_previousDisplayNameMapping = null;
|
||||
|
||||
ColumnsWithRelationships = delegationInfo.ColumnsWithRelationships;
|
||||
}
|
||||
|
||||
public TableDelegationInfo DelegationInfo => _delegationInfo;
|
||||
|
||||
private readonly TableDelegationInfo _delegationInfo;
|
||||
|
||||
private readonly DType _type;
|
||||
|
||||
private readonly InternalDataEntityMetadataProvider _externalDataEntityMetadataProvider;
|
||||
|
||||
private readonly InternalTableMetadata _externalTableMetadata;
|
||||
|
||||
private readonly DelegationMetadataBase _delegationMetadata;
|
||||
|
||||
private readonly TabularDataQueryOptions _tabularDataQueryOptions;
|
||||
|
||||
private readonly BidirectionalDictionary<string, string> _displayNameMapping;
|
||||
|
||||
private readonly BidirectionalDictionary<string, string> _previousDisplayNameMapping;
|
||||
|
||||
TabularDataQueryOptions IExternalTabularDataSource.QueryOptions => _tabularDataQueryOptions;
|
||||
|
||||
public string Name => EntityName.Value;
|
||||
|
||||
public DName EntityName { get; }
|
||||
|
||||
public bool IsSelectable => _delegationInfo.SelectionRestriction == null ? false : _delegationInfo.SelectionRestriction.IsSelectable;
|
||||
|
||||
public bool IsDelegatable => _delegationInfo.IsDelegable;
|
||||
|
||||
public bool IsRefreshable => true;
|
||||
|
||||
public bool RequiresAsync => true;
|
||||
|
||||
public bool IsWritable { get; }
|
||||
|
||||
public bool IsClearable => throw new System.NotImplementedException();
|
||||
|
||||
IExternalDataEntityMetadataProvider IExternalDataSource.DataEntityMetadataProvider => _externalDataEntityMetadataProvider;
|
||||
|
||||
DataSourceKind IExternalDataSource.Kind => DataSourceKind.Connected;
|
||||
|
||||
IExternalTableMetadata IExternalDataSource.TableMetadata => _externalTableMetadata;
|
||||
|
||||
IDelegationMetadata IExternalDataSource.DelegationMetadata => _delegationMetadata;
|
||||
|
||||
DType IExternalEntity.Type => _type;
|
||||
|
||||
public RecordType RecordType { get; }
|
||||
|
||||
public bool IsPageable => _delegationInfo.PagingCapabilities.IsOnlyServerPagable || IsDelegatable;
|
||||
|
||||
public bool IsConvertingDisplayNameMapping => false;
|
||||
|
||||
BidirectionalDictionary<string, string> IDisplayMapped<string>.DisplayNameMapping => _displayNameMapping;
|
||||
|
||||
BidirectionalDictionary<string, string> IDisplayMapped<string>.PreviousDisplayNameMapping => _previousDisplayNameMapping;
|
||||
|
||||
public bool HasCachedCountRows => false;
|
||||
|
||||
public IReadOnlyList<string> GetKeyColumns() => _externalTableMetadata?.KeyColumns ?? new List<string>();
|
||||
|
||||
IEnumerable<string> IExternalTabularDataSource.GetKeyColumns(IExpandInfo expandInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool CanIncludeSelect(string selectColumnName) => _externalTableMetadata != null && _externalTableMetadata.CanIncludeSelect(selectColumnName);
|
||||
|
||||
bool IExternalTabularDataSource.CanIncludeSelect(IExpandInfo expandInfo, string selectColumnName)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo expandToAdd)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
bool IExternalTabularDataSource.CanIncludeExpand(IExpandInfo parentExpandInfo, IExpandInfo expandToAdd)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
private static List<OperationCapabilityMetadata> GetCapabilityMetadata(AggregateType recordType, TableDelegationInfo delegationInfo)
|
||||
{
|
||||
DType type = recordType._type;
|
||||
|
||||
DPath GetDPath(string prop) => DPath.Root.Append(new DName(prop));
|
||||
|
||||
void AddOrUpdate(Dictionary<DPath, DelegationCapability> dic, string prop, DelegationCapability capability)
|
||||
{
|
||||
DPath dPath = GetDPath(prop);
|
||||
|
||||
if (!dic.TryGetValue(dPath, out DelegationCapability existingCapability))
|
||||
{
|
||||
dic.Add(dPath, capability);
|
||||
}
|
||||
else
|
||||
{
|
||||
dic[dPath] = new DelegationCapability(existingCapability.Capabilities | capability.Capabilities);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<DPath, DelegationCapability> groupByRestrictions = new Dictionary<DPath, DelegationCapability>();
|
||||
|
||||
if (delegationInfo?.GroupRestriction?.UngroupableProperties != null)
|
||||
{
|
||||
foreach (string ungroupableProperty in delegationInfo.GroupRestriction.UngroupableProperties)
|
||||
{
|
||||
AddOrUpdate(groupByRestrictions, ungroupableProperty, DelegationCapability.Group);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<DPath, DelegationCapability> sortRestrictions = new Dictionary<DPath, DelegationCapability>();
|
||||
|
||||
if (delegationInfo?.SortRestriction?.UnsortableProperties != null)
|
||||
{
|
||||
foreach (string unsortableProperty in delegationInfo.SortRestriction.UnsortableProperties)
|
||||
{
|
||||
AddOrUpdate(sortRestrictions, unsortableProperty, DelegationCapability.Sort);
|
||||
}
|
||||
}
|
||||
|
||||
if (delegationInfo?.SortRestriction?.AscendingOnlyProperties != null)
|
||||
{
|
||||
foreach (string ascendingOnlyProperty in delegationInfo.SortRestriction.AscendingOnlyProperties)
|
||||
{
|
||||
AddOrUpdate(sortRestrictions, ascendingOnlyProperty, DelegationCapability.SortAscendingOnly);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<DPath, DPath> oDataReplacements = new Dictionary<DPath, DPath>();
|
||||
|
||||
FilterOpMetadata filterOpMetadata = new CdpFilterOpMetadata(recordType, delegationInfo);
|
||||
GroupOpMetadata groupOpMetadata = new GroupOpMetadata(type, groupByRestrictions);
|
||||
ODataOpMetadata oDataOpMetadata = new ODataOpMetadata(type, oDataReplacements);
|
||||
SortOpMetadata sortOpMetadata = new SortOpMetadata(type, sortRestrictions);
|
||||
|
||||
return new List<OperationCapabilityMetadata>()
|
||||
{
|
||||
filterOpMetadata,
|
||||
groupOpMetadata,
|
||||
oDataOpMetadata,
|
||||
sortOpMetadata
|
||||
};
|
||||
}
|
||||
|
||||
internal static DPath GetReplacementPath(string alias, DPath currentColumnPath)
|
||||
{
|
||||
if (alias.Contains("/"))
|
||||
{
|
||||
var fullPath = DPath.Root;
|
||||
|
||||
foreach (var name in alias.Split('/'))
|
||||
{
|
||||
fullPath = fullPath.Append(new DName(name));
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Task 5593666: This is temporary to not cause regressions while sharepoint switches to using full query param
|
||||
return currentColumnPath.Append(new DName(alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,9 +7,9 @@ using Microsoft.PowerFx.Core.Functions.Delegation;
|
|||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.UtilityDataStructures;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
internal class CdpDataSourceMetadata : IDataEntityMetadata
|
||||
internal class InternalDataEntityMetadata : IDataEntityMetadata
|
||||
{
|
||||
public string EntityName { get; private set; }
|
||||
|
||||
|
@ -33,7 +33,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
public IExternalTableDefinition EntityDefinition => throw new NotImplementedException();
|
||||
|
||||
public CdpDataSourceMetadata(string entityName, string datasetName, BidirectionalDictionary<string, string> displayNameMapping = null)
|
||||
public InternalDataEntityMetadata(string entityName, string datasetName, BidirectionalDictionary<string, string> displayNameMapping = null)
|
||||
{
|
||||
EntityName = entityName;
|
||||
DatasetName = datasetName;
|
|
@ -5,19 +5,19 @@ using System.Collections.Generic;
|
|||
using Microsoft.PowerFx.Core.Entities.Delegation;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
internal class CdpEntityMetadataProvider : IExternalDataEntityMetadataProvider
|
||||
internal class InternalDataEntityMetadataProvider : IExternalDataEntityMetadataProvider
|
||||
{
|
||||
private readonly Dictionary<string, IDataEntityMetadata> _entityIdToMetadataMap;
|
||||
|
||||
public CdpEntityMetadataProvider()
|
||||
public InternalDataEntityMetadataProvider()
|
||||
{
|
||||
_entityIdToMetadataMap = new Dictionary<string, IDataEntityMetadata>();
|
||||
}
|
||||
|
||||
|
||||
public void AddSource(string sourceName, IDataEntityMetadata tabularDataSource)
|
||||
{
|
||||
{
|
||||
_entityIdToMetadataMap.Add(sourceName, tabularDataSource);
|
||||
}
|
||||
|
144
src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalTableMetadata.cs
поставляемый
Normal file
144
src/libraries/Microsoft.PowerFx.Core/Entities/External/InternalTableMetadata.cs
поставляемый
Normal file
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerFx.Core.App;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
internal class InternalTableMetadata : IExternalTableMetadata
|
||||
{
|
||||
private readonly RecordType _type;
|
||||
|
||||
public InternalTableMetadata(RecordType recordType, string name, string displayName, bool isReadOnly, string parameterPkColumnName = "")
|
||||
{
|
||||
Contracts.AssertNonEmpty(name);
|
||||
Contracts.AssertNonEmpty(displayName);
|
||||
|
||||
_type = recordType;
|
||||
|
||||
Name = name;
|
||||
DisplayName = displayName;
|
||||
IsReadOnly = isReadOnly;
|
||||
|
||||
ParameterPkColumnName = parameterPkColumnName;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
|
||||
public string ParameterPkColumnName { get; }
|
||||
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public IReadOnlyList<string> KeyColumns => _type._type.DisplayNameProvider.LogicalToDisplayPairs.Select(pair => pair.Key.Value).Where(col => col != "57dfb1b5-7d79-4046-a4da-fd831d5befe1-KeyId").ToList();
|
||||
|
||||
public IReadOnlyList<ColumnMetadata> Columns { get; }
|
||||
|
||||
public ColumnMetadata this[string columnName]
|
||||
{
|
||||
get
|
||||
{
|
||||
Contracts.AssertNonEmpty(columnName);
|
||||
return TryGetColumn(columnName, out ColumnMetadata columnMetadata) ? columnMetadata : null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetColumn(string columnName, out ColumnMetadata column)
|
||||
{
|
||||
string GetDisplayName(string fieldName) => _type._type.DisplayNameProvider == null || !_type._type.DisplayNameProvider.TryGetDisplayName(new DName(fieldName), out DName displayName) ? fieldName : displayName.Value;
|
||||
DataFormat? ToDataFormat(DType dType) => dType.Kind switch
|
||||
{
|
||||
DKind.Record or DKind.Table or DKind.OptionSetValue => DataFormat.Lookup,
|
||||
DKind.String or DKind.Decimal or DKind.Number or DKind.Currency => DataFormat.AllowedValues,
|
||||
_ => null
|
||||
};
|
||||
|
||||
Contracts.AssertNonEmpty(columnName);
|
||||
|
||||
if (_type.TryGetUnderlyingFieldType(columnName, out FormulaType ft))
|
||||
{
|
||||
column = new ColumnMetadata(
|
||||
columnName,
|
||||
ft._type,
|
||||
ToDataFormat(ft._type),
|
||||
GetDisplayName(columnName),
|
||||
false, // is read-only
|
||||
false, // primary key
|
||||
false, // isRequired
|
||||
ColumnCreationKind.UserProvided,
|
||||
ColumnVisibility.Default,
|
||||
columnName,
|
||||
columnName,
|
||||
columnName,
|
||||
null, // columnLookupMetadata
|
||||
null); // attachmentMetadata
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
column = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether specified column can be included in select query option.
|
||||
/// </summary>
|
||||
/// <param name="selectColumnName"></param>
|
||||
internal bool CanIncludeSelect(string selectColumnName)
|
||||
{
|
||||
DType colType = DType.Unknown;
|
||||
bool hasColumn = TryGetColumn(selectColumnName, out ColumnMetadata columnMetadata);
|
||||
|
||||
if (hasColumn)
|
||||
{
|
||||
colType = columnMetadata.Type;
|
||||
}
|
||||
|
||||
return hasColumn && !colType.IsAttachment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether specified navigation column can be included in expand query option.
|
||||
/// </summary>
|
||||
/// <param name="expand"></param>
|
||||
internal bool CanIncludeExpand(IExpandInfo expand)
|
||||
{
|
||||
string fieldName = expand.PolymorphicParent ?? expand.Name;
|
||||
|
||||
DType colType = DType.Unknown;
|
||||
bool hasColumn = TryGetColumn(fieldName, out ColumnMetadata columnMetadata);
|
||||
|
||||
if (hasColumn)
|
||||
{
|
||||
colType = columnMetadata.Type;
|
||||
}
|
||||
|
||||
return hasColumn && (colType.Kind == DKind.Record || colType.Kind == DKind.DataEntity || (colType.Kind == DKind.Polymorphic && ValidatePolymorphicExpand(expand, colType)));
|
||||
}
|
||||
|
||||
private bool ValidatePolymorphicExpand(IExpandInfo expand, DType colType)
|
||||
{
|
||||
if (!colType.HasPolymorphicInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (colType.PolymorphicInfo.TargetFields.Contains(expand.Name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Owner types have different metadata compared to other polymorphics,
|
||||
// they use the same field for patching but have different ones for relationships
|
||||
// which are always hard-coded to 'owning<user/team>'.
|
||||
return colType.PolymorphicInfo.TargetFields.Contains("ownerid") && (expand.Name == "owninguser" || expand.Name == "owningteam");
|
||||
}
|
||||
}
|
||||
}
|
289
src/libraries/Microsoft.PowerFx.Core/Entities/External/TableDelegationInfo.cs
поставляемый
Normal file
289
src/libraries/Microsoft.PowerFx.Core/Entities/External/TableDelegationInfo.cs
поставляемый
Normal file
|
@ -0,0 +1,289 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Entities
|
||||
{
|
||||
// Supports delegation information for CDP connectors
|
||||
public abstract class TableDelegationInfo
|
||||
{
|
||||
// Defines unsortable columns or columns only supporting ascending ordering
|
||||
public SortRestrictions SortRestriction { get; init; }
|
||||
|
||||
// Defines columns that cannot be sorted and required properties
|
||||
public FilterRestrictions FilterRestriction { get; init; }
|
||||
|
||||
// Used to indicate whether this table has selectable columns
|
||||
public SelectionRestrictions SelectionRestriction { get; init; }
|
||||
|
||||
// Defines ungroupable columns
|
||||
public GroupRestrictions GroupRestriction { get; init; }
|
||||
|
||||
// Filter functions supported by all columns of the table
|
||||
public IEnumerable<DelegationOperator> FilterFunctions { get; init; }
|
||||
|
||||
// Filter functions supported by the table
|
||||
public IEnumerable<DelegationOperator> FilterSupportedFunctions { get; init; }
|
||||
|
||||
// Defines paging capabilities
|
||||
internal PagingCapabilities PagingCapabilities { get; init; }
|
||||
|
||||
// Defining per column capabilities
|
||||
internal IReadOnlyCollection<KeyValuePair<string, ColumnCapabilitiesBase>> ColumnsCapabilities { get; init; }
|
||||
|
||||
// Supports per record permission
|
||||
internal bool SupportsRecordPermission { get; init; }
|
||||
|
||||
// Logical name of table
|
||||
public string TableName { get; init; }
|
||||
|
||||
// Read-Only table
|
||||
public bool IsReadOnly { get; init; }
|
||||
|
||||
// Dataset name
|
||||
public string DatasetName { get; init; }
|
||||
|
||||
// Defines columns with relationships
|
||||
// Key = field logical name, Value = foreign table logical name
|
||||
internal Dictionary<string, string> ColumnsWithRelationships { get; init; }
|
||||
|
||||
public virtual bool IsDelegable => (SortRestriction != null) || (FilterRestriction != null) || (FilterFunctions != null);
|
||||
|
||||
public TableDelegationInfo()
|
||||
{
|
||||
PagingCapabilities = new PagingCapabilities()
|
||||
{
|
||||
IsOnlyServerPagable = false,
|
||||
ServerPagingOptions = new string[0]
|
||||
};
|
||||
SupportsRecordPermission = true;
|
||||
ColumnsWithRelationships = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public abstract ColumnCapabilitiesDefinition GetColumnCapability(string fieldName);
|
||||
}
|
||||
|
||||
internal sealed class ComplexColumnCapabilities : ColumnCapabilitiesBase
|
||||
{
|
||||
internal Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
|
||||
|
||||
public ComplexColumnCapabilities()
|
||||
{
|
||||
_childColumnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>();
|
||||
}
|
||||
|
||||
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
|
||||
{
|
||||
Contracts.AssertNonEmpty(name);
|
||||
Contracts.AssertValue(capability);
|
||||
|
||||
_childColumnsCapabilities.Add(name, capability);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ColumnCapabilities : ColumnCapabilitiesBase
|
||||
{
|
||||
public IReadOnlyDictionary<string, ColumnCapabilitiesBase> Properties => _childColumnsCapabilities.Any() ? _childColumnsCapabilities : null;
|
||||
|
||||
private Dictionary<string, ColumnCapabilitiesBase> _childColumnsCapabilities;
|
||||
|
||||
private ColumnCapabilitiesDefinition _capabilities;
|
||||
|
||||
// Those are default CDS filter supported functions
|
||||
// From // PowerApps-Client\src\Language\PowerFx.Dataverse.Parser\Importers\DataDescription\CdsCapabilities.cs
|
||||
public static readonly IEnumerable<DelegationOperator> DefaultFilterFunctionSupport = new DelegationOperator[]
|
||||
{
|
||||
DelegationOperator.And,
|
||||
DelegationOperator.Arraylookup,
|
||||
DelegationOperator.Astype,
|
||||
DelegationOperator.Average,
|
||||
DelegationOperator.Cdsin,
|
||||
DelegationOperator.Contains,
|
||||
DelegationOperator.Count,
|
||||
DelegationOperator.Countdistinct,
|
||||
DelegationOperator.Endswith,
|
||||
DelegationOperator.Eq,
|
||||
DelegationOperator.Ge,
|
||||
DelegationOperator.Gt,
|
||||
DelegationOperator.Le,
|
||||
DelegationOperator.Lt,
|
||||
DelegationOperator.Max,
|
||||
DelegationOperator.Min,
|
||||
DelegationOperator.Ne,
|
||||
DelegationOperator.Not,
|
||||
DelegationOperator.Null,
|
||||
DelegationOperator.Or,
|
||||
DelegationOperator.Startswith,
|
||||
DelegationOperator.Sum,
|
||||
DelegationOperator.Top
|
||||
};
|
||||
|
||||
public static ColumnCapabilities DefaultColumnCapabilities => new ColumnCapabilities()
|
||||
{
|
||||
_capabilities = new ColumnCapabilitiesDefinition()
|
||||
{
|
||||
FilterFunctions = DefaultFilterFunctionSupport,
|
||||
QueryAlias = null,
|
||||
IsChoice = null
|
||||
},
|
||||
_childColumnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>()
|
||||
};
|
||||
|
||||
private ColumnCapabilities()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)
|
||||
{
|
||||
Contracts.AssertNonEmpty(name);
|
||||
Contracts.AssertValue(capability);
|
||||
|
||||
_childColumnsCapabilities.Add(name, capability);
|
||||
}
|
||||
|
||||
public ColumnCapabilities(ColumnCapabilitiesDefinition capability)
|
||||
{
|
||||
Contracts.AssertValueOrNull(capability);
|
||||
|
||||
_capabilities = capability;
|
||||
_childColumnsCapabilities = new Dictionary<string, ColumnCapabilitiesBase>();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ColumnCapabilitiesDefinition
|
||||
{
|
||||
public IEnumerable<DelegationOperator> FilterFunctions
|
||||
{
|
||||
get => _filterFunctions ?? ColumnCapabilities.DefaultFilterFunctionSupport;
|
||||
init => _filterFunctions = value;
|
||||
}
|
||||
|
||||
// Used in PowerApps-Client/src/AppMagic/js/Core/Core.Data/ConnectedDataDeserialization/TabularDataDeserialization.ts
|
||||
// Used by SP connector only to rename column logical names in OData queries
|
||||
internal string QueryAlias { get; init; }
|
||||
|
||||
// Sharepoint delegation specific
|
||||
internal bool? IsChoice { get; init; }
|
||||
|
||||
private IEnumerable<DelegationOperator> _filterFunctions;
|
||||
|
||||
public ColumnCapabilitiesDefinition()
|
||||
{
|
||||
}
|
||||
|
||||
internal DelegationCapability ToDelegationCapability()
|
||||
{
|
||||
DelegationCapability columnDelegationCapability = DelegationCapability.None;
|
||||
|
||||
foreach (DelegationOperator columnFilterFunctionEnum in FilterFunctions)
|
||||
{
|
||||
string columnFilterFunction = columnFilterFunctionEnum.ToString().ToLowerInvariant();
|
||||
|
||||
if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(columnFilterFunction, out DelegationCapability filterFunctionCapability))
|
||||
{
|
||||
columnDelegationCapability |= filterFunctionCapability;
|
||||
}
|
||||
}
|
||||
|
||||
columnDelegationCapability |= DelegationCapability.Filter;
|
||||
|
||||
return columnDelegationCapability;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ColumnCapabilitiesBase
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class PagingCapabilities
|
||||
{
|
||||
// Defines is the tabular connector is supporting client or server paging
|
||||
// If true, @odata.nextlink URL is used instead of $skip and $top query parameters
|
||||
// If false, $top and $skip will be used
|
||||
public bool IsOnlyServerPagable { get; init; }
|
||||
|
||||
// Only supported values "top" and "skiptoken"
|
||||
// Used to define paging options to use
|
||||
public IEnumerable<string> ServerPagingOptions { get; init; }
|
||||
|
||||
public PagingCapabilities()
|
||||
{
|
||||
}
|
||||
|
||||
public PagingCapabilities(bool isOnlyServerPagable, string[] serverPagingOptions)
|
||||
{
|
||||
// Server paging restrictions, true for CDS
|
||||
// Setting 'IsOnlyServerPagable' to true in the table metadata response lets PowerApps application to use
|
||||
// @odata.nextlink URL in reponse message (instead of $skip and $top query parameters) for page traversal.
|
||||
// It is also required to set sortable and filterable restrictions for PowerApps to page through results.
|
||||
IsOnlyServerPagable = isOnlyServerPagable;
|
||||
|
||||
// List of supported server-driven paging capabilities, null for CDS
|
||||
// ex: top, skiptoken
|
||||
// used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/AppMagic/js/AppMagic.Services/ConnectedData/CdpConnector.ts&_a=contents&version=GBmaster
|
||||
ServerPagingOptions = serverPagingOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GroupRestrictions
|
||||
{
|
||||
// Defines properties can cannot be grouped
|
||||
public IList<string> UngroupableProperties { get; init; }
|
||||
|
||||
public GroupRestrictions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SelectionRestrictions
|
||||
{
|
||||
// Indicates whether this table has selectable columns ($select)
|
||||
// Columns with an Attachment will be excluded
|
||||
// Used in https://msazure.visualstudio.com/OneAgile/_git/PowerApps-Client?path=/src/Cloud/DocumentServer.Core/Document/Document/InfoTypes/CdsDataSourceInfo.cs&_a=contents&version=GBmaster
|
||||
public bool IsSelectable { get; init; }
|
||||
|
||||
public SelectionRestrictions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FilterRestrictions
|
||||
{
|
||||
// List of required properties
|
||||
public IList<string> RequiredProperties { get; init; }
|
||||
|
||||
// List of non filterable properties (like images)
|
||||
public IList<string> NonFilterableProperties { get; init; }
|
||||
|
||||
public FilterRestrictions()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SortRestrictions
|
||||
{
|
||||
// Columns only supported ASC ordering
|
||||
public IList<string> AscendingOnlyProperties { get; init; }
|
||||
|
||||
// Columns that don't support ordering
|
||||
public IList<string> UnsortableProperties { get; init; }
|
||||
|
||||
public SortRestrictions()
|
||||
{
|
||||
}
|
||||
|
||||
public SortRestrictions(IList<string> unsortableProperties, IList<string> ascendingOnlyProperties)
|
||||
{
|
||||
// List of properties which support ascending order only
|
||||
AscendingOnlyProperties = ascendingOnlyProperties;
|
||||
|
||||
// List of unsortable properties
|
||||
UnsortableProperties = unsortableProperties;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,16 +3,20 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Functions.Delegation
|
||||
{
|
||||
// This lightweight wrapper around DelegationCababilityConstants is used to enforce valid values for capabilities.
|
||||
[DebuggerDisplay("Delegation={DebugString}")]
|
||||
internal struct DelegationCapability
|
||||
{
|
||||
private BigInteger _capabilities;
|
||||
|
||||
private static readonly Lazy<Dictionary<BinaryOp, DelegationCapability>> _binaryOpToDelegationCapabilityMap =
|
||||
new Lazy<Dictionary<BinaryOp, DelegationCapability>>(
|
||||
() => new Dictionary<BinaryOp, DelegationCapability>
|
||||
|
@ -180,6 +184,305 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation
|
|||
|
||||
public BigInteger Capabilities => _capabilities;
|
||||
|
||||
internal string DebugString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_capabilities.IsZero)
|
||||
{
|
||||
return nameof(None);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (HasCapability(Sort))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Sort));
|
||||
}
|
||||
|
||||
if (HasCapability(Filter))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Filter));
|
||||
}
|
||||
|
||||
if (HasCapability(GreaterThan))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(GreaterThan));
|
||||
}
|
||||
|
||||
if (HasCapability(GreaterThanOrEqual))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(GreaterThanOrEqual));
|
||||
}
|
||||
|
||||
if (HasCapability(LessThan))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(LessThan));
|
||||
}
|
||||
|
||||
if (HasCapability(LessThanOrEqual))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(LessThanOrEqual));
|
||||
}
|
||||
|
||||
if (HasCapability(And))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(And));
|
||||
}
|
||||
|
||||
if (HasCapability(Or))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Or));
|
||||
}
|
||||
|
||||
if (HasCapability(In))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(In));
|
||||
}
|
||||
|
||||
if (HasCapability(Exactin))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Exactin));
|
||||
}
|
||||
|
||||
if (HasCapability(Not))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Not));
|
||||
}
|
||||
|
||||
if (HasCapability(Equal))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Equal));
|
||||
}
|
||||
|
||||
if (HasCapability(NotEqual))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(NotEqual));
|
||||
}
|
||||
|
||||
if (HasCapability(SortAscendingOnly))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(SortAscendingOnly));
|
||||
}
|
||||
|
||||
if (HasCapability(Contains))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Contains));
|
||||
}
|
||||
|
||||
if (HasCapability(IndexOf))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(IndexOf));
|
||||
}
|
||||
|
||||
if (HasCapability(SubStringOf))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(SubStringOf));
|
||||
}
|
||||
|
||||
if (HasCapability(Year))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Year));
|
||||
}
|
||||
|
||||
if (HasCapability(Month))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Month));
|
||||
}
|
||||
|
||||
if (HasCapability(Day))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Day));
|
||||
}
|
||||
|
||||
if (HasCapability(Hour))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Hour));
|
||||
}
|
||||
|
||||
if (HasCapability(Minute))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Minute));
|
||||
}
|
||||
|
||||
if (HasCapability(Second))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Second));
|
||||
}
|
||||
|
||||
if (HasCapability(Lower))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Lower));
|
||||
}
|
||||
|
||||
if (HasCapability(Upper))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Upper));
|
||||
}
|
||||
|
||||
if (HasCapability(Trim))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Trim));
|
||||
}
|
||||
|
||||
if (HasCapability(Null))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Null));
|
||||
}
|
||||
|
||||
if (HasCapability(Date))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Date));
|
||||
}
|
||||
|
||||
if (HasCapability(Length))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Length));
|
||||
}
|
||||
|
||||
if (HasCapability(Sum))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Sum));
|
||||
}
|
||||
|
||||
if (HasCapability(Min))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Min));
|
||||
}
|
||||
|
||||
if (HasCapability(Max))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Max));
|
||||
}
|
||||
|
||||
if (HasCapability(Average))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Average));
|
||||
}
|
||||
|
||||
if (HasCapability(Count))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Count));
|
||||
}
|
||||
|
||||
if (HasCapability(Add))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Add));
|
||||
}
|
||||
|
||||
if (HasCapability(Sub))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Sub));
|
||||
}
|
||||
|
||||
if (HasCapability(StartsWith))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(StartsWith));
|
||||
}
|
||||
|
||||
if (HasCapability(Mul))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Mul));
|
||||
}
|
||||
|
||||
if (HasCapability(Div))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Div));
|
||||
}
|
||||
|
||||
if (HasCapability(EndsWith))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(EndsWith));
|
||||
}
|
||||
|
||||
if (HasCapability(CountDistinct))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(CountDistinct));
|
||||
}
|
||||
|
||||
if (HasCapability(CdsIn))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(CdsIn));
|
||||
}
|
||||
|
||||
if (HasCapability(Top))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Top));
|
||||
}
|
||||
|
||||
if (HasCapability(Group))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(Group));
|
||||
}
|
||||
|
||||
if (HasCapability(AsType))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(AsType));
|
||||
}
|
||||
|
||||
if (HasCapability(ArrayLookup))
|
||||
{
|
||||
AddCommaIfNeeded(sb);
|
||||
sb.Append(nameof(ArrayLookup));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCommaIfNeeded(StringBuilder sb)
|
||||
{
|
||||
if (sb.Length != 0)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsValid(BigInteger capabilityConstant)
|
||||
{
|
||||
return (capabilityConstant == None) ||
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
||||
{
|
||||
internal class CdpFilterOpMetadata : FilterOpMetadata
|
||||
{
|
||||
private readonly TableDelegationInfo _delegationInfo;
|
||||
|
||||
public CdpFilterOpMetadata(AggregateType schema, TableDelegationInfo delegationInfo)
|
||||
: base(schema._type, null, null, GetFilterFunctionsSupportedByAllColumns(delegationInfo), GetFilterFunctionSupportedByTable(delegationInfo))
|
||||
{
|
||||
_delegationInfo = delegationInfo;
|
||||
}
|
||||
|
||||
private static DelegationCapability GetFilterFunctionsSupportedByAllColumns(TableDelegationInfo delegationInfo)
|
||||
{
|
||||
DelegationCapability filterFunctionSupportedByAllColumns = DelegationCapability.None;
|
||||
|
||||
if (delegationInfo?.FilterFunctions != null)
|
||||
{
|
||||
foreach (DelegationOperator globalFilterFunctionEnum in delegationInfo.FilterFunctions)
|
||||
{
|
||||
string globalFilterFunction = globalFilterFunctionEnum.ToString().ToLowerInvariant();
|
||||
|
||||
if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(globalFilterFunction, out DelegationCapability globalFilterFunctionCapability))
|
||||
{
|
||||
filterFunctionSupportedByAllColumns |= globalFilterFunctionCapability | DelegationCapability.Filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filterFunctionSupportedByAllColumns;
|
||||
}
|
||||
|
||||
private static DelegationCapability? GetFilterFunctionSupportedByTable(TableDelegationInfo delegationInfo)
|
||||
{
|
||||
DelegationCapability? filterFunctionsSupportedByTable = null;
|
||||
|
||||
if (delegationInfo?.FilterSupportedFunctions != null)
|
||||
{
|
||||
filterFunctionsSupportedByTable = DelegationCapability.None;
|
||||
|
||||
foreach (DelegationOperator globalSupportedFilterFunctionEnum in delegationInfo.FilterSupportedFunctions)
|
||||
{
|
||||
string globalSupportedFilterFunction = globalSupportedFilterFunctionEnum.ToString().ToLowerInvariant();
|
||||
|
||||
if (DelegationCapability.OperatorToDelegationCapabilityMap.TryGetValue(globalSupportedFilterFunction, out DelegationCapability globalSupportedFilterFunctionCapability))
|
||||
{
|
||||
filterFunctionsSupportedByTable |= globalSupportedFilterFunctionCapability | DelegationCapability.Filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filterFunctionsSupportedByTable;
|
||||
}
|
||||
|
||||
public override bool TryGetColumnCapabilities(DPath columnPath, out DelegationCapability capabilities)
|
||||
{
|
||||
Contracts.AssertValid(columnPath);
|
||||
ColumnCapabilitiesDefinition columnCapabilityDefinition = _delegationInfo.GetColumnCapability(columnPath.Name.Value);
|
||||
|
||||
if (columnCapabilityDefinition != null)
|
||||
{
|
||||
capabilities = columnCapabilityDefinition.ToDelegationCapability();
|
||||
return true;
|
||||
}
|
||||
|
||||
capabilities = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,36 +8,32 @@ using Microsoft.PowerFx.Core.Utils;
|
|||
namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
||||
{
|
||||
// Defines filter operation metadata.
|
||||
internal sealed class FilterOpMetadata : OperationCapabilityMetadata
|
||||
internal class FilterOpMetadata : OperationCapabilityMetadata
|
||||
{
|
||||
private readonly Dictionary<DPath, DelegationCapability> _columnCapabilities;
|
||||
|
||||
private readonly Dictionary<DPath, DelegationCapability> _columnRestrictions;
|
||||
|
||||
private readonly DelegationCapability? _filterFunctionsSupportedByTable;
|
||||
|
||||
// Filter functions supported at the table level.
|
||||
// If no capability at column level specified then this would be the default filter functionality supported by column.
|
||||
private readonly DelegationCapability _defaultCapabilities;
|
||||
private readonly DelegationCapability _defaultCapabilities;
|
||||
|
||||
public FilterOpMetadata(
|
||||
DType tableSchema,
|
||||
Dictionary<DPath, DelegationCapability> columnRestrictions,
|
||||
Dictionary<DPath, DelegationCapability> columnCapabilities,
|
||||
DelegationCapability filterFunctionsSupportedByAllColumns,
|
||||
DelegationCapability? filterFunctionsSupportedByTable)
|
||||
public FilterOpMetadata(DType tableSchema, Dictionary<DPath, DelegationCapability> columnRestrictions, Dictionary<DPath, DelegationCapability> columnCapabilities, DelegationCapability filterFunctionsSupportedByAllColumns, DelegationCapability? filterFunctionsSupportedByTable)
|
||||
: base(tableSchema)
|
||||
{
|
||||
Contracts.AssertValue(columnRestrictions);
|
||||
Contracts.AssertValue(columnCapabilities);
|
||||
|
||||
{
|
||||
_columnCapabilities = columnCapabilities;
|
||||
_columnRestrictions = columnRestrictions;
|
||||
|
||||
_filterFunctionsSupportedByTable = filterFunctionsSupportedByTable;
|
||||
_defaultCapabilities = filterFunctionsSupportedByAllColumns;
|
||||
|
||||
if (_filterFunctionsSupportedByTable != null)
|
||||
{
|
||||
_defaultCapabilities = filterFunctionsSupportedByAllColumns | DelegationCapability.Filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Dictionary<DPath, DelegationCapability> ColumnRestrictions => _columnRestrictions;
|
||||
|
||||
|
@ -55,7 +51,7 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
|||
{
|
||||
// If there are no capabilities defined at column level then filter is not supported.
|
||||
// Otherwise this simply means that filter operators at table level are not supported.
|
||||
// For example, Filter(CDS, Lower(Col1) != Lower(Col2)), here != operator at table level needs to be supported as it's not operating on any column directly.
|
||||
// For example, Filter(CDS, Lower(Col1) != Lower(Col2)), here != operator at table level needs to be supported as it's not operating on any column directly.
|
||||
if (DefaultColumnCapabilities.Capabilities == DelegationCapability.None)
|
||||
{
|
||||
return DelegationCapability.None;
|
||||
|
@ -71,7 +67,7 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
|||
public override bool TryGetColumnCapabilities(DPath columnPath, out DelegationCapability capabilities)
|
||||
{
|
||||
Contracts.AssertValid(columnPath);
|
||||
|
||||
|
||||
// See if there is a specific capability defined for column.
|
||||
// If not then just return default one.
|
||||
if (!_columnCapabilities.TryGetValue(columnPath, out capabilities))
|
||||
|
|
|
@ -52,4 +52,48 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation
|
|||
public const string AsType = "astype";
|
||||
public const string ArrayLookup = "arraylookup";
|
||||
}
|
||||
|
||||
public enum DelegationOperator
|
||||
{
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Le,
|
||||
Gt,
|
||||
Ge,
|
||||
And,
|
||||
Or,
|
||||
Contains,
|
||||
Indexof,
|
||||
Substringof,
|
||||
Not,
|
||||
Year,
|
||||
Month,
|
||||
Day,
|
||||
Hour,
|
||||
Minute,
|
||||
Second,
|
||||
Tolower,
|
||||
Toupper,
|
||||
Trim,
|
||||
Null,
|
||||
Date,
|
||||
Length,
|
||||
Sum,
|
||||
Min,
|
||||
Max,
|
||||
Average,
|
||||
Count,
|
||||
Add,
|
||||
Sub,
|
||||
Startswith,
|
||||
Mul,
|
||||
Div,
|
||||
Endswith,
|
||||
Countdistinct,
|
||||
Cdsin,
|
||||
Top,
|
||||
Astype,
|
||||
Arraylookup
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.PowerFx.Core;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
@ -58,6 +57,13 @@ namespace Microsoft.PowerFx.Types
|
|||
return true;
|
||||
}
|
||||
|
||||
// When a relationship is used in connected types, this returns the 'base' type of the field
|
||||
// without retrieving the type issued from the relationship
|
||||
public virtual bool TryGetUnderlyingFieldType(string name, out FormulaType type)
|
||||
{
|
||||
return TryGetFieldType(name, out type);
|
||||
}
|
||||
|
||||
internal bool TryGetBackingDType(string fieldName, out DType type)
|
||||
{
|
||||
if (_type == null)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.PowerFx.Core;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
|
@ -43,12 +43,29 @@ namespace Microsoft.PowerFx.Types
|
|||
/// Derived classes calling this must override <see cref="AggregateType.FieldNames"/>
|
||||
/// and <see cref="AggregateType.TryGetFieldType(string, out FormulaType)"/>.
|
||||
/// </summary>
|
||||
/// <param name="displayNameProvider">Provide DispayNamerovide to be used.</param>
|
||||
/// <param name="displayNameProvider">Provide DisplayNameProvider to be used.</param>
|
||||
public RecordType(DisplayNameProvider displayNameProvider)
|
||||
: base(false, displayNameProvider)
|
||||
{
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RecordType"/> class with <see cref="DisplayNameProvider"/> and <see cref="TableDelegationInfo"/>.
|
||||
/// Derived classes calling this must override <see cref="AggregateType.TryGetFieldType(string, out FormulaType)"/>.
|
||||
/// </summary>
|
||||
/// <param name="displayNameProvider">Provide DisplayNameProvider to be used.</param>
|
||||
/// <param name="delegationInfo">Table provider to be used.</param>
|
||||
public RecordType(DisplayNameProvider displayNameProvider, TableDelegationInfo delegationInfo)
|
||||
: base(false, displayNameProvider)
|
||||
{
|
||||
_type = DType.AttachDataSourceInfo(_type, new DataSourceInfo(this, displayNameProvider, delegationInfo));
|
||||
_fieldNames = displayNameProvider.LogicalToDisplayPairs.Select(pair => pair.Key.Value).ToList();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> FieldNames => _fieldNames;
|
||||
|
||||
private readonly IEnumerable<string> _fieldNames = null;
|
||||
|
||||
public override void Visit(ITypeVisitor vistor)
|
||||
{
|
||||
vistor.Visit(this);
|
||||
|
|
|
@ -239,7 +239,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
for (var i = 1; i < args.Count - 1; i += 2)
|
||||
{
|
||||
base.TryGetColumnLogicalName(dsType, binding.Features.SupportColumnNamesAsIdentifiers, args[i], DefaultErrorContainer, out var columnName, out var columnType).Verify();
|
||||
Contracts.Assert(dsType.Contains(columnName));
|
||||
Contracts.Assert(dsType.Kind == DKind.LazyTable || dsType.Contains(columnName));
|
||||
|
||||
retval |= dsType.AssociateDataSourcesToSelect(dataSourceToQueryOptionsMap, columnName, columnType, true);
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
var columnType = typedName.Type;
|
||||
var columnName = typedName.Name.Value;
|
||||
|
||||
Contracts.Assert(dsType.Contains(new DName(columnName)));
|
||||
Contracts.Assert(dsType.Kind == DKind.LazyTable || dsType.Contains(new DName(columnName)));
|
||||
|
||||
retval |= dsType.AssociateDataSourcesToSelect(dataSourceToQueryOptionsMap, columnName, columnType, true);
|
||||
}
|
||||
|
|
|
@ -515,7 +515,7 @@ namespace Microsoft.PowerFx.Core.Types
|
|||
AssertValid();
|
||||
}
|
||||
|
||||
internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null)
|
||||
internal DType(LazyTypeProvider provider, bool isTable, DisplayNameProvider displayNameProvider = null, HashSet<IExternalTabularDataSource> associatedDataSources = null)
|
||||
{
|
||||
Contracts.AssertValue(provider);
|
||||
|
||||
|
@ -528,7 +528,7 @@ namespace Microsoft.PowerFx.Core.Types
|
|||
ExpandInfo = null;
|
||||
PolymorphicInfo = null;
|
||||
Metadata = null;
|
||||
AssociatedDataSources = new HashSet<IExternalTabularDataSource>();
|
||||
AssociatedDataSources = associatedDataSources ?? new HashSet<IExternalTabularDataSource>();
|
||||
OptionSetInfo = null;
|
||||
ViewInfo = null;
|
||||
NamedValueKind = null;
|
||||
|
@ -1069,7 +1069,7 @@ namespace Microsoft.PowerFx.Core.Types
|
|||
case DKind.Record:
|
||||
return this;
|
||||
case DKind.LazyTable:
|
||||
return new DType(LazyTypeProvider, isTable: false, DisplayNameProvider);
|
||||
return new DType(LazyTypeProvider, isTable: false, DisplayNameProvider, AssociatedDataSources);
|
||||
case DKind.Table:
|
||||
case DKind.DataEntity:
|
||||
case DKind.Control:
|
||||
|
@ -1113,7 +1113,7 @@ namespace Microsoft.PowerFx.Core.Types
|
|||
case DKind.Table:
|
||||
return this;
|
||||
case DKind.LazyRecord:
|
||||
return new DType(LazyTypeProvider, isTable: true, DisplayNameProvider);
|
||||
return new DType(LazyTypeProvider, isTable: true, DisplayNameProvider, AssociatedDataSources);
|
||||
case DKind.Record:
|
||||
case DKind.DataEntity:
|
||||
case DKind.Control:
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
||||
|
@ -13,9 +11,12 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
|||
internal sealed partial class DelegationMetadata : DelegationMetadataBase
|
||||
{
|
||||
public DelegationMetadata(DType schema, string delegationMetadataJson)
|
||||
: base(
|
||||
schema: schema,
|
||||
compositeMetadata: new DelegationMetadataParser().Parse(delegationMetadataJson, schema))
|
||||
: base(schema, new DelegationMetadataParser().Parse(delegationMetadataJson, schema))
|
||||
{
|
||||
}
|
||||
|
||||
public DelegationMetadata(DType schema, List<OperationCapabilityMetadata> metadata)
|
||||
: base(schema, new CompositeCapabilityMetadata(schema, metadata))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
|||
|
||||
if (propertyCapabilityJsonObject.TryGetProperty(CapabilitiesConstants.PropertyQueryAlias, out var alias))
|
||||
{
|
||||
oDataReplacement.Add(propertyPath, GetReplacementPath(alias.GetString(), columnPath));
|
||||
oDataReplacement.Add(propertyPath, Entities.DataSourceInfo.GetReplacementPath(alias.GetString(), columnPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,26 +62,6 @@ namespace Microsoft.PowerFx.Core.Functions.Delegation.DelegationMetadata
|
|||
|
||||
return new ODataOpMetadata(schema, oDataReplacement);
|
||||
}
|
||||
|
||||
private DPath GetReplacementPath(string alias, DPath currentColumnPath)
|
||||
{
|
||||
if (alias.Contains("/"))
|
||||
{
|
||||
var fullPath = DPath.Root;
|
||||
|
||||
foreach (var name in alias.Split('/'))
|
||||
{
|
||||
fullPath = fullPath.Append(new DName(name));
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Task 5593666: This is temporary to not cause regressions while sharepoint switches to using full query param
|
||||
return currentColumnPath.Append(new DName(alias));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,9 +246,9 @@ namespace Microsoft.PowerFx.Types
|
|||
{
|
||||
var name = pair.Name;
|
||||
var value = pair.Value;
|
||||
FormulaType fieldType = null;
|
||||
FormulaType fieldType = null;
|
||||
|
||||
if (recordType?.TryGetFieldType(name, out fieldType) == false)
|
||||
if (recordType?.TryGetUnderlyingFieldType(name, out fieldType) == false)
|
||||
{
|
||||
if (!settings.AllowUnknownRecordFields)
|
||||
{
|
||||
|
|
|
@ -33,22 +33,22 @@ namespace Microsoft.PowerFx.Tests
|
|||
|
||||
string text = (string)LoggingTestServer.GetFileText(@"Responses\Compatibility GetSchema.json");
|
||||
|
||||
ConnectorType ctCdp = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _);
|
||||
ConnectorType ctPa = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _);
|
||||
ConnectorType ctSw = ConnectorFunction.GetConnectorTypeAndTableCapabilities(tableResolver, "name", "Schema/Items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _);
|
||||
ConnectorType ctCdp = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.CdpCompatibility, "dataset", out _, out _, out _);
|
||||
ConnectorType ctPa = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.PowerAppsCompatibility, "dataset", out _, out _, out _);
|
||||
ConnectorType ctSw = ConnectorFunction.GetCdpTableType(tableResolver, "name", "schema/items", StringValue.New(text), null, ConnectorCompatibility.SwaggerCompatibility, "dataset", out _, out _, out _);
|
||||
|
||||
string cdp = ctCdp.FormulaType._type.ToString();
|
||||
string pa = ctPa.FormulaType._type.ToString();
|
||||
string sw = ctSw.FormulaType._type.ToString();
|
||||
string cdp = ctCdp.FormulaType.ToStringWithDisplayNames();
|
||||
string pa = ctPa.FormulaType.ToStringWithDisplayNames();
|
||||
string sw = ctSw.FormulaType.ToStringWithDisplayNames();
|
||||
|
||||
// CDP compatibility: priority is an enum, when "format": "enum" isn't present
|
||||
Assert.Equal("![Id1:s, Id3:s, Id4:s, priority:l, priority2:l]", cdp);
|
||||
Assert.Equal<object>("r![Id1`'User ID 1':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:l, priority2`'Priority 2':l]", cdp);
|
||||
|
||||
// Swagger compatibility: priority is a string as "format": "enum" isn't present
|
||||
Assert.Equal("![Id1:s, Id3:s, Id4:s, priority:s, priority2:l]", sw);
|
||||
Assert.Equal<object>("r![Id1`'User ID 1':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:s, priority2`'Priority 2':l]", sw);
|
||||
|
||||
// PA compatibility: Id2 is internal and present (not the case for CDP/Swagger compatibilities)
|
||||
Assert.Equal("![Id1:s, Id2:s, Id3:s, Id4:s, priority:s, priority2:l]", pa);
|
||||
Assert.Equal<object>("r![Id1`'User ID 1':s, Id2`'User ID 2':s, Id3`'User ID 3':s, Id4`'User ID 4':s, priority`Priority:s, priority2`'Priority 2':l]", pa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
@ -41,7 +43,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
|
||||
// This one is not delegatable
|
||||
Assert.False(fileTable.IsDelegable);
|
||||
Assert.Equal("*[line:s]", fileTable.Type._type.ToString());
|
||||
|
||||
// Lazy type
|
||||
Assert.Equal("r*", fileTable.Type._type.ToString());
|
||||
|
||||
PowerFxConfig config = new PowerFxConfig(Features.PowerFxV1);
|
||||
RecalcEngine engine = new RecalcEngine(config);
|
||||
|
@ -59,7 +63,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
StringValue str = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("b", str.Value);
|
||||
|
||||
RecordType trt = fileTable.TabularRecordType;
|
||||
RecordType trt = fileTable.RecordType;
|
||||
Assert.NotNull(trt);
|
||||
}
|
||||
}
|
||||
|
@ -73,17 +77,17 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
_fileName = File.Exists(fileName) ? fileName : throw new FileNotFoundException($"File not found: {_fileName}");
|
||||
}
|
||||
|
||||
public override bool IsDelegable => false;
|
||||
|
||||
public override ConnectorType ConnectorType => null;
|
||||
public override bool IsDelegable => false;
|
||||
|
||||
// No need for files
|
||||
public override HttpClient HttpClient => null;
|
||||
|
||||
internal override IReadOnlyDictionary<string, Relationship> Relationships => null;
|
||||
|
||||
// Initialization can be synchronous
|
||||
public void Init()
|
||||
{
|
||||
SetRecordType(RecordType.Empty().Add("line", FormulaType.String));
|
||||
RecordType = new FileTabularRecordType(RecordType.Empty().Add("line", FormulaType.String));
|
||||
}
|
||||
|
||||
protected override async Task<IReadOnlyCollection<DValue<RecordValue>>> GetItemsInternalAsync(IServiceProvider serviceProvider, ODataParameters oDataParameters, CancellationToken cancellationToken)
|
||||
|
@ -99,4 +103,45 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
return lines.Select(line => DValue<RecordValue>.Of(FormulaValue.NewRecordFromFields(new NamedValue("line", FormulaValue.New(line))))).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal class FileTabularRecordType : RecordType
|
||||
{
|
||||
internal readonly RecordType _recordType;
|
||||
|
||||
public FileTabularRecordType(RecordType recordType)
|
||||
: base(GetDisplayNameProvider(recordType), GetDelegationInfo())
|
||||
{
|
||||
_recordType = recordType;
|
||||
}
|
||||
|
||||
private static TableDelegationInfo GetDelegationInfo()
|
||||
{
|
||||
return new CdpDelegationInfo()
|
||||
{
|
||||
TableName = "FileTabular"
|
||||
};
|
||||
}
|
||||
|
||||
private static DisplayNameProvider GetDisplayNameProvider(RecordType recordType) => DisplayNameProvider.New(recordType.FieldNames.Select(f => new KeyValuePair<Core.Utils.DName, Core.Utils.DName>(new Core.Utils.DName(f), new Core.Utils.DName(f))));
|
||||
|
||||
public override bool TryGetFieldType(string name, out FormulaType type)
|
||||
{
|
||||
return _recordType.TryGetFieldType(name, out type);
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (other == null || other is not FileTabularRecordType other2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _recordType == other2._recordType;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,6 +152,12 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
|
||||
private static BlobValue GetBlobFromFile(string file, bool base64)
|
||||
{
|
||||
if (!Path.IsPathRooted(file))
|
||||
{
|
||||
string root = Directory.GetCurrentDirectory();
|
||||
file = Path.Combine(root, file);
|
||||
}
|
||||
|
||||
byte[] bytes = File.ReadAllBytes(file);
|
||||
return new BlobValue(new ByteArrayBlob(bytes));
|
||||
}
|
||||
|
|
|
@ -2000,52 +2000,57 @@ POST https://tip1-shared-002.azure-apim.net/invoke
|
|||
string ft = returnType.FormulaType.ToStringWithDisplayNames();
|
||||
|
||||
string expected =
|
||||
"!['@odata.nextLink'`'Next link':s, value:*[Array:!['@odata.id'`'OData Id':s, _createdby_value`'Created By (Value)':s, '_createdby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Type)':s, " +
|
||||
"_createdbyexternalparty_value`'Created By (External Party) (Value)':s, '_createdbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (External Party) (Type)':s, _createdonbehalfby_value`'Created " +
|
||||
"By (Delegate) (Value)':s, '_createdonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Delegate) (Type)':s, _defaultpricelevelid_value`'Price List (Value)':s, '_defaultpricelevelid_value@Microsoft.Dynamics.CRM.lookuplogic" +
|
||||
"alname'`'Price List (Type)':s, _masterid_value`'Master ID (Value)':s, '_masterid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Master ID (Type)':s, _modifiedby_value`'Modified By (Value)':s, '_modifiedby_value@Microsoft.Dynamics.CRM.looku" +
|
||||
"plogicalname'`'Modified By (Type)':s, _modifiedbyexternalparty_value`'Modified By (External Party) (Value)':s, '_modifiedbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (External " +
|
||||
"Party) (Type)':s, _modifiedonbehalfby_value`'Modified By (Delegate) (Value)':s, '_modifiedonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Delegate) (Type)':s, _msa_managingpartnerid_value`'Managing " +
|
||||
"Partner (Value)':s, '_msa_managingpartnerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Managing Partner (Type)':s, _msdyn_accountkpiid_value`'KPI (Value)':s, '_msdyn_accountkpiid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'KPI " +
|
||||
"(Type)':s, _msdyn_salesaccelerationinsightid_value`'Sales Acceleration Insights ID (Value)':s, '_msdyn_salesaccelerationinsightid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Sales Acceleration Insights " +
|
||||
"ID (Type)':s, _originatingleadid_value`'Originating Lead (Value)':s, '_originatingleadid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Originating Lead (Type)':s, _ownerid_value`'Owner (Value)':s, '_ownerid_value@Microsoft.Dynamics.CRM.lo" +
|
||||
"okuplogicalname'`'Owner (Type)':s, _owningbusinessunit_value`'Owning Business Unit (Value)':s, '_owningbusinessunit_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning Business Unit (Type)':s, _owningteam_value`'Owning " +
|
||||
"Team (Value)':s, '_owningteam_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning Team (Type)':s, _owninguser_value`'Owning User (Value)':s, '_owninguser_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning " +
|
||||
"User (Type)':s, _parentaccountid_value`'Parent Account (Value)':s, '_parentaccountid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Parent Account (Type)':s, _preferredequipmentid_value`'Preferred Facility/Equipment " +
|
||||
"(Value)':s, '_preferredequipmentid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred Facility/Equipment (Type)':s, _preferredserviceid_value`'Preferred Service (Value)':s, '_preferredserviceid_value@Microsoft.Dynamics.CRM.lookuplogi" +
|
||||
"calname'`'Preferred Service (Type)':s, _preferredsystemuserid_value`'Preferred User (Value)':s, '_preferredsystemuserid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred User (Type)':s, _primarycontactid_value`'Primary " +
|
||||
"Contact (Value)':s, '_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Primary Contact (Type)':s, _slaid_value`'SLA (Value)':s, '_slaid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'SLA " +
|
||||
"(Type)':s, _slainvokedid_value`'Last SLA applied (Value)':s, '_slainvokedid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Last SLA applied (Type)':s, _territoryid_value`'Territory (Value)':s, '_territoryid_value@Microsoft.Dynamics.CRM.loo" +
|
||||
"kuplogicalname'`'Territory (Type)':s, _transactioncurrencyid_value`'Currency (Value)':s, '_transactioncurrencyid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Currency (Type)':s, accountcategorycode`Category:w, " +
|
||||
"accountclassificationcode`Classification:w, accountid`Account:s, accountnumber`'Account Number':s, accountratingcode`'Account Rating':w, address1_addressid`'Address 1: ID':s, address1_addresstypecode`'Address " +
|
||||
"1: Address Type':w, address1_city`'Address 1: City':s, address1_composite`'Address 1':s, address1_country`'Address 1: Country/Region':s, address1_county`'Address 1: County':s, address1_fax`'Address 1: " +
|
||||
"Fax':s, address1_freighttermscode`'Address 1: Freight Terms':w, address1_latitude`'Address 1: Latitude':w, address1_line1`'Address 1: Street 1':s, address1_line2`'Address 1: Street 2':s, address1_line3`'Address " +
|
||||
"1: Street 3':s, address1_longitude`'Address 1: Longitude':w, address1_name`'Address 1: Name':s, address1_postalcode`'Address 1: ZIP/Postal Code':s, address1_postofficebox`'Address 1: Post Office Box':s, " +
|
||||
"address1_primarycontactname`'Address 1: Primary Contact Name':s, address1_shippingmethodcode`'Address 1: Shipping Method':w, address1_stateorprovince`'Address 1: State/Province':s, address1_telephone1`'Address " +
|
||||
"Phone':s, address1_telephone2`'Address 1: Telephone 2':s, address1_telephone3`'Address 1: Telephone 3':s, address1_upszone`'Address 1: UPS Zone':s, address1_utcoffset`'Address 1: UTC Offset':w, address2_addressid`'Address " +
|
||||
"2: ID':s, address2_addresstypecode`'Address 2: Address Type':w, address2_city`'Address 2: City':s, address2_composite`'Address 2':s, address2_country`'Address 2: Country/Region':s, address2_county`'Address " +
|
||||
"2: County':s, address2_fax`'Address 2: Fax':s, address2_freighttermscode`'Address 2: Freight Terms':w, address2_latitude`'Address 2: Latitude':w, address2_line1`'Address 2: Street 1':s, address2_line2`'Address " +
|
||||
"2: Street 2':s, address2_line3`'Address 2: Street 3':s, address2_longitude`'Address 2: Longitude':w, address2_name`'Address 2: Name':s, address2_postalcode`'Address 2: ZIP/Postal Code':s, address2_postofficebox`'Address " +
|
||||
"2: Post Office Box':s, address2_primarycontactname`'Address 2: Primary Contact Name':s, address2_shippingmethodcode`'Address 2: Shipping Method':w, address2_stateorprovince`'Address 2: State/Province':s, " +
|
||||
"address2_telephone1`'Address 2: Telephone 1':s, address2_telephone2`'Address 2: Telephone 2':s, address2_telephone3`'Address 2: Telephone 3':s, address2_upszone`'Address 2: UPS Zone':s, address2_utcoffset`'Address " +
|
||||
"2: UTC Offset':w, adx_createdbyipaddress`'Created By (IP Address)':s, adx_createdbyusername`'Created By (User Name)':s, adx_modifiedbyipaddress`'Modified By (IP Address)':s, adx_modifiedbyusername`'Modified " +
|
||||
"By (User Name)':s, aging30`'Aging 30':w, aging30_base`'Aging 30 (Base)':w, aging60`'Aging 60':w, aging60_base`'Aging 60 (Base)':w, aging90`'Aging 90':w, aging90_base`'Aging 90 (Base)':w, businesstypecode`'Business " +
|
||||
"Type':w, createdon`'Created On':d, creditlimit`'Credit Limit':w, creditlimit_base`'Credit Limit (Base)':w, creditonhold`'Credit Hold':b, customersizecode`'Customer Size':w, customertypecode`'Relationship " +
|
||||
"Type':w, description`Description:s, donotbulkemail`'Do not allow Bulk Emails':b, donotbulkpostalmail`'Do not allow Bulk Mails':b, donotemail`'Do not allow Emails':b, donotfax`'Do not allow Faxes':b, donotphone`'Do " +
|
||||
"not allow Phone Calls':b, donotpostalmail`'Do not allow Mails':b, donotsendmm`'Send Marketing Materials':b, emailaddress1`Email:s, emailaddress2`'Email Address 2':s, emailaddress3`'Email Address 3':s, " +
|
||||
"entityimage`'Default Image':s, entityimageid`'Entity Image Id':s, exchangerate`'Exchange Rate':w, fax`Fax:s, followemail`'Follow Email Activity':b, ftpsiteurl`'FTP Site':s, importsequencenumber`'Import " +
|
||||
"Sequence Number':w, industrycode`Industry:w, lastonholdtime`'Last On Hold Time':d, lastusedincampaign`'Last Date Included in Campaign':d, marketcap`'Market Capitalization':w, marketcap_base`'Market Capitalization " +
|
||||
"(Base)':w, marketingonly`'Marketing Only':b, merged`Merged:b, modifiedon`'Modified On':d, msdyn_gdproptout`'GDPR Optout':b, name`'Account Name':s, numberofemployees`'Number of Employees':w, onholdtime`'On " +
|
||||
"Hold Time (Minutes)':w, opendeals`'Open Deals':w, opendeals_date`'Open Deals (Last Updated On)':d, opendeals_state`'Open Deals (State)':w, openrevenue`'Open Revenue':w, openrevenue_base`'Open Revenue (Base)':w, " +
|
||||
"openrevenue_date`'Open Revenue (Last Updated On)':d, openrevenue_state`'Open Revenue (State)':w, overriddencreatedon`'Record Created On':d, ownershipcode`Ownership:w, participatesinworkflow`'Participates " +
|
||||
"in Workflow':b, paymenttermscode`'Payment Terms':w, preferredappointmentdaycode`'Preferred Day':w, preferredappointmenttimecode`'Preferred Time':w, preferredcontactmethodcode`'Preferred Method of Contact':w, " +
|
||||
"primarysatoriid`'Primary Satori ID':s, primarytwitterid`'Primary Twitter ID':s, processid`Process:s, revenue`'Annual Revenue':w, revenue_base`'Annual Revenue (Base)':w, sharesoutstanding`'Shares Outstanding':w, " +
|
||||
"shippingmethodcode`'Shipping Method':w, sic`'SIC Code':s, stageid`'(Deprecated) Process Stage':s, statecode`Status:w, statuscode`'Status Reason':w, stockexchange`'Stock Exchange':s, teamsfollowed`TeamsFollowed:w, " +
|
||||
"telephone1`'Main Phone':s, telephone2`'Other Phone':s, telephone3`'Telephone 3':s, territorycode`'Territory Code':w, tickersymbol`'Ticker Symbol':s, timespentbymeonemailandmeetings`'Time Spent by me':s, " +
|
||||
"timezoneruleversionnumber`'Time Zone Rule Version Number':w, traversedpath`'(Deprecated) Traversed Path':s, utcconversiontimezonecode`'UTC Conversion Time Zone Code':w, versionnumber`'Version Number':w, " +
|
||||
"websiteurl`Website:s, yominame`'Yomi Account Name':s]]]";
|
||||
"!['@odata.nextLink'`'Next link':s, value:*[Array:!['@odata.id'`'OData Id':s, _createdby_value`'Created By (Value)':s, '_createdby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created " +
|
||||
"By (Type)':s, _createdbyexternalparty_value`'Created By (External Party) (Value)':s, '_createdbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (External Party) " +
|
||||
"(Type)':s, _createdonbehalfby_value`'Created By (Delegate) (Value)':s, '_createdonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Created By (Delegate) (Type)':s, _defaultpricelevelid_value`'" +
|
||||
"Price List (Value)':s, '_defaultpricelevelid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Price List (Type)':s, _masterid_value`'Master ID (Value)':s, '_masterid_value@Microsoft.Dynamics.CRM.lookup" +
|
||||
"logicalname'`'Master ID (Type)':s, _modifiedby_value`'Modified By (Value)':s, '_modifiedby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Type)':s, _modifiedbyexternalparty_value`'Modifi" +
|
||||
"ed By (External Party) (Value)':s, '_modifiedbyexternalparty_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (External Party) (Type)':s, _modifiedonbehalfby_value`'Modified " +
|
||||
"By (Delegate) (Value)':s, '_modifiedonbehalfby_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Modified By (Delegate) (Type)':s, _msa_managingpartnerid_value`'Managing Partner (Value)':s, " +
|
||||
"'_msa_managingpartnerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Managing Partner (Type)':s, _msdyn_accountkpiid_value`'KPI (Value)':s, '_msdyn_accountkpiid_value@Microsoft.Dynamics.CRM.lookupl" +
|
||||
"ogicalname'`'KPI (Type)':s, _msdyn_salesaccelerationinsightid_value`'Sales Acceleration Insights ID (Value)':s, '_msdyn_salesaccelerationinsightid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Sales" +
|
||||
" Acceleration Insights ID (Type)':s, _originatingleadid_value`'Originating Lead (Value)':s, '_originatingleadid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Originating Lead (Type)':s, " +
|
||||
"_ownerid_value`'Owner (Value)':s, '_ownerid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owner (Type)':s, _owningbusinessunit_value`'Owning Business Unit (Value)':s, '_owningbusinessunit_value@Micr" +
|
||||
"osoft.Dynamics.CRM.lookuplogicalname'`'Owning Business Unit (Type)':s, _owningteam_value`'Owning Team (Value)':s, '_owningteam_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning " +
|
||||
"Team (Type)':s, _owninguser_value`'Owning User (Value)':s, '_owninguser_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Owning User (Type)':s, _parentaccountid_value`'Parent Account " +
|
||||
"(Value)':s, '_parentaccountid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Parent Account (Type)':s, _preferredequipmentid_value`'Preferred Facility/Equipment (Value)':s, '_preferredequipmentid_val" +
|
||||
"ue@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred Facility/Equipment (Type)':s, _preferredserviceid_value`'Preferred Service (Value)':s, '_preferredserviceid_value@Microsoft.Dynamics.CRM.lookuplo" +
|
||||
"gicalname'`'Preferred Service (Type)':s, _preferredsystemuserid_value`'Preferred User (Value)':s, '_preferredsystemuserid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Preferred " +
|
||||
"User (Type)':s, _primarycontactid_value`'Primary Contact (Value)':s, '_primarycontactid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Primary Contact (Type)':s, _slaid_value`'SLA " +
|
||||
"(Value)':s, '_slaid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'SLA (Type)':s, _slainvokedid_value`'Last SLA applied (Value)':s, '_slainvokedid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Las" +
|
||||
"t SLA applied (Type)':s, _territoryid_value`'Territory (Value)':s, '_territoryid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Territory (Type)':s, _transactioncurrencyid_value`'Currency " +
|
||||
"(Value)':s, '_transactioncurrencyid_value@Microsoft.Dynamics.CRM.lookuplogicalname'`'Currency (Type)':s, accountcategorycode`Category:w, accountclassificationcode`Classification:w, " +
|
||||
"accountid`Account:s, accountnumber`'Account Number':s, accountratingcode`'Account Rating':w, address1_addressid`'Address 1: ID':s, address1_addresstypecode`'Address 1: Address Type':w, " +
|
||||
"address1_city`'Address 1: City':s, address1_composite`'Address 1':s, address1_country`'Address 1: Country/Region':s, address1_county`'Address 1: County':s, address1_fax`'Address 1: " +
|
||||
"Fax':s, address1_freighttermscode`'Address 1: Freight Terms':w, address1_latitude`'Address 1: Latitude':w, address1_line1`'Address 1: Street 1':s, address1_line2`'Address 1: Street " +
|
||||
"2':s, address1_line3`'Address 1: Street 3':s, address1_longitude`'Address 1: Longitude':w, address1_name`'Address 1: Name':s, address1_postalcode`'Address 1: ZIP/Postal Code':s, address1_postofficebox`" +
|
||||
"'Address 1: Post Office Box':s, address1_primarycontactname`'Address 1: Primary Contact Name':s, address1_shippingmethodcode`'Address 1: Shipping Method':w, address1_stateorprovince`'Address " +
|
||||
"1: State/Province':s, address1_telephone1`'Address Phone':s, address1_telephone2`'Address 1: Telephone 2':s, address1_telephone3`'Address 1: Telephone 3':s, address1_upszone`'Address " +
|
||||
"1: UPS Zone':s, address1_utcoffset`'Address 1: UTC Offset':w, address2_addressid`'Address 2: ID':s, address2_addresstypecode`'Address 2: Address Type':w, address2_city`'Address 2: City':s, " +
|
||||
"address2_composite`'Address 2':s, address2_country`'Address 2: Country/Region':s, address2_county`'Address 2: County':s, address2_fax`'Address 2: Fax':s, address2_freighttermscode`'Address " +
|
||||
"2: Freight Terms':w, address2_latitude`'Address 2: Latitude':w, address2_line1`'Address 2: Street 1':s, address2_line2`'Address 2: Street 2':s, address2_line3`'Address 2: Street 3':s, " +
|
||||
"address2_longitude`'Address 2: Longitude':w, address2_name`'Address 2: Name':s, address2_postalcode`'Address 2: ZIP/Postal Code':s, address2_postofficebox`'Address 2: Post Office Box':s, " +
|
||||
"address2_primarycontactname`'Address 2: Primary Contact Name':s, address2_shippingmethodcode`'Address 2: Shipping Method':w, address2_stateorprovince`'Address 2: State/Province':s, " +
|
||||
"address2_telephone1`'Address 2: Telephone 1':s, address2_telephone2`'Address 2: Telephone 2':s, address2_telephone3`'Address 2: Telephone 3':s, address2_upszone`'Address 2: UPS Zone':s, " +
|
||||
"address2_utcoffset`'Address 2: UTC Offset':w, adx_createdbyipaddress`'Created By (IP Address)':s, adx_createdbyusername`'Created By (User Name)':s, adx_modifiedbyipaddress`'Modified " +
|
||||
"By (IP Address)':s, adx_modifiedbyusername`'Modified By (User Name)':s, aging30`'Aging 30':w, aging30_base`'Aging 30 (Base)':w, aging60`'Aging 60':w, aging60_base`'Aging 60 (Base)':w, " +
|
||||
"aging90`'Aging 90':w, aging90_base`'Aging 90 (Base)':w, businesstypecode`'Business Type':w, createdon`'Created On':d, creditlimit`'Credit Limit':w, creditlimit_base`'Credit Limit (Base)':w, " +
|
||||
"creditonhold`'Credit Hold':b, customersizecode`'Customer Size':w, customertypecode`'Relationship Type':w, description`Description:s, donotbulkemail`'Do not allow Bulk Emails':b, donotbulkpostalmail`'Do" +
|
||||
" not allow Bulk Mails':b, donotemail`'Do not allow Emails':b, donotfax`'Do not allow Faxes':b, donotphone`'Do not allow Phone Calls':b, donotpostalmail`'Do not allow Mails':b, donotsendmm`'Send " +
|
||||
"Marketing Materials':b, emailaddress1`Email:s, emailaddress2`'Email Address 2':s, emailaddress3`'Email Address 3':s, entityimage`'Default Image':s, entityimageid`'Entity Image Id':s, " +
|
||||
"exchangerate`'Exchange Rate':w, fax`Fax:s, followemail`'Follow Email Activity':b, ftpsiteurl`'FTP Site':s, importsequencenumber`'Import Sequence Number':w, industrycode`Industry:w, " +
|
||||
"lastonholdtime`'Last On Hold Time':d, lastusedincampaign`'Last Date Included in Campaign':d, marketcap`'Market Capitalization':w, marketcap_base`'Market Capitalization (Base)':w, marketingonly`'Marketi" +
|
||||
"ng Only':b, merged`Merged:b, modifiedon`'Modified On':d, msdyn_gdproptout`'GDPR Optout':b, name`'Account Name':s, numberofemployees`'Number of Employees':w, onholdtime`'On Hold Time " +
|
||||
"(Minutes)':w, opendeals`'Open Deals':w, opendeals_date`'Open Deals (Last Updated On)':d, opendeals_state`'Open Deals (State)':w, openrevenue`'Open Revenue':w, openrevenue_base`'Open " +
|
||||
"Revenue (Base)':w, openrevenue_date`'Open Revenue (Last Updated On)':d, openrevenue_state`'Open Revenue (State)':w, overriddencreatedon`'Record Created On':d, ownershipcode`Ownership:w, " +
|
||||
"participatesinworkflow`'Participates in Workflow':b, paymenttermscode`'Payment Terms':w, preferredappointmentdaycode`'Preferred Day':w, preferredappointmenttimecode`'Preferred Time':w, " +
|
||||
"preferredcontactmethodcode`'Preferred Method of Contact':w, primarysatoriid`'Primary Satori ID':s, primarytwitterid`'Primary Twitter ID':s, processid`Process:s, revenue`'Annual Revenue':w, " +
|
||||
"revenue_base`'Annual Revenue (Base)':w, sharesoutstanding`'Shares Outstanding':w, shippingmethodcode`'Shipping Method':w, sic`'SIC Code':s, stageid`'(Deprecated) Process Stage':s, statecode`Status:w, " +
|
||||
"statuscode`'Status Reason':w, stockexchange`'Stock Exchange':s, teamsfollowed`TeamsFollowed:w, telephone1`'Main Phone':s, telephone2`'Other Phone':s, telephone3`'Telephone 3':s, territorycode`'Territor" +
|
||||
"y Code':w, tickersymbol`'Ticker Symbol':s, timespentbymeonemailandmeetings`'Time Spent by me':s, timezoneruleversionnumber`'Time Zone Rule Version Number':w, traversedpath`'(Deprecated) " +
|
||||
"Traversed Path':s, utcconversiontimezonecode`'UTC Conversion Time Zone Code':w, versionnumber`'Version Number':w, websiteurl`Website:s, yominame`'Yomi Account Name':s]]]";
|
||||
|
||||
Assert.Equal(expected, ft);
|
||||
Assert.Equal<object>(expected, ft);
|
||||
Assert.Equal("address1_addresstypecode", returnType.Fields[0].Fields[0].Fields[7].Name);
|
||||
Assert.Equal("w", returnType.Fields[0].Fields[0].Fields[7].FormulaType.ToStringWithDisplayNames());
|
||||
Assert.True(returnType.Fields[0].Fields[0].Fields[7].IsEnum);
|
||||
|
|
|
@ -15,6 +15,8 @@ using Microsoft.PowerFx.Types;
|
|||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
#pragma warning disable SA1116
|
||||
|
||||
namespace Microsoft.PowerFx.Connectors.Tests
|
||||
{
|
||||
public class PowerPlatformTabularTests
|
||||
|
@ -97,40 +99,26 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
CdpTableValue sqlTable = connectorTable.GetTableValue();
|
||||
Assert.True(sqlTable._tabularService.IsInitialized);
|
||||
Assert.True(sqlTable.IsDelegable);
|
||||
Assert.Equal("*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type._type.ToString());
|
||||
Assert.Equal("r*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
HashSet<IExternalTabularDataSource> ads = sqlTable.Type._type.AssociatedDataSources;
|
||||
Assert.NotNull(ads);
|
||||
|
||||
// Tests skipped as ConnectorType.AddDataSource is skipping the creation of AssociatedDataSources
|
||||
#if false
|
||||
Assert.Single(ads);
|
||||
|
||||
TabularDataSource tds = Assert.IsType<TabularDataSource>(ads.First());
|
||||
Assert.NotNull(tds);
|
||||
Assert.NotNull(tds.DataEntityMetadataProvider);
|
||||
DataSourceInfo dataSourceInfo = Assert.IsType<DataSourceInfo>(ads.First());
|
||||
Assert.NotNull(dataSourceInfo);
|
||||
|
||||
CdpEntityMetadataProvider cemp = Assert.IsType<CdpEntityMetadataProvider>(tds.DataEntityMetadataProvider);
|
||||
Assert.True(cemp.TryGetEntityMetadata("Customers", out IDataEntityMetadata dem));
|
||||
Assert.Equal("Customers", dataSourceInfo.EntityName.Value);
|
||||
Assert.True(dataSourceInfo.IsDelegatable);
|
||||
Assert.True(dataSourceInfo.IsPageable);
|
||||
Assert.True(dataSourceInfo.IsRefreshable);
|
||||
Assert.True(dataSourceInfo.IsSelectable);
|
||||
Assert.True(dataSourceInfo.IsWritable);
|
||||
|
||||
TabularDataSourceMetadata tdsm = Assert.IsType<TabularDataSourceMetadata>(dem);
|
||||
Assert.Equal("pfxdev-sql.database.windows.net,connectortest", tdsm.DatasetName);
|
||||
Assert.Equal("Customers", tdsm.EntityName);
|
||||
Assert.Equal("Customers", dataSourceInfo.Name);
|
||||
Assert.True(dataSourceInfo.RequiresAsync);
|
||||
|
||||
Assert.Equal("Customers", tds.EntityName.Value);
|
||||
Assert.True(tds.IsDelegatable);
|
||||
Assert.True(tds.IsPageable);
|
||||
Assert.True(tds.IsRefreshable);
|
||||
Assert.True(tds.IsSelectable);
|
||||
Assert.True(tds.IsWritable);
|
||||
Assert.Equal(DataSourceKind.Connected, tds.Kind);
|
||||
Assert.Equal("Customers", tds.Name);
|
||||
Assert.True(tds.RequiresAsync);
|
||||
Assert.NotNull(tds.ServiceCapabilities);
|
||||
#endif
|
||||
|
||||
Assert.NotNull(sqlTable._connectorType);
|
||||
Assert.Null(sqlTable._connectorType.Relationships);
|
||||
Assert.Null(sqlTable.Relationships);
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Customers", sqlTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -224,13 +212,15 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
CdpTableValue sqlTable = connectorTable.GetTableValue();
|
||||
Assert.True(sqlTable._tabularService.IsInitialized);
|
||||
Assert.True(sqlTable.IsDelegable);
|
||||
Assert.Equal("*[Color:s, DiscontinuedDate:d, ListPrice:w, ModifiedDate:d, Name:s, ProductCategoryID:w, ProductID:w, ProductModelID:w, ProductNumber:s, SellEndDate:d, SellStartDate:d, Size:s, StandardCost:w, ThumbNailPhoto:o, ThumbnailPhotoFileName:s, Weight:w, rowguid:s]", sqlTable.Type._type.ToString());
|
||||
|
||||
// Note relationships to ProductCategory and ProductModel with ~ notation
|
||||
Assert.Equal<object>("r*[Color:s, DiscontinuedDate:d, ListPrice:w, ModifiedDate:d, Name:s, ProductCategoryID:~[SalesLT].[ProductCategory]:w, ProductID:w, ProductModelID:~[SalesLT].[ProductModel]:w, ProductNumber:s, " +
|
||||
"SellEndDate:d, SellStartDate:d, Size:s, StandardCost:w, ThumbNailPhoto:o, ThumbnailPhotoFileName:s, Weight:w, rowguid:s]", sqlTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
HashSet<IExternalTabularDataSource> ads = sqlTable.Type._type.AssociatedDataSources;
|
||||
Assert.NotNull(ads);
|
||||
|
||||
Assert.NotNull(sqlTable._connectorType);
|
||||
Assert.Null(sqlTable._connectorType.Relationships); // TO BE CHANGED, x-ms-relationships only for now
|
||||
Assert.Null(sqlTable.Relationships); // TO BE CHANGED, x-ms-relationships only for now
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add(fxTableName, sqlTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -247,13 +237,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
StringValue address = Assert.IsType<StringValue>(result);
|
||||
Assert.Equal("HL Road Frame - Black, 58", address.Value);
|
||||
|
||||
bool b = sqlTable.TabularRecordType.TryGetFieldExternalTableName("ProductModelID", out string externalTableName, out string foreignKey);
|
||||
bool b = sqlTable.RecordType.TryGetFieldExternalTableName("ProductModelID", out string externalTableName, out string foreignKey);
|
||||
Assert.True(b);
|
||||
Assert.Equal("[SalesLT].[ProductModel]", externalTableName); // Logical Name
|
||||
Assert.Equal("ProductModelID", foreignKey);
|
||||
|
||||
testConnector.SetResponseFromFiles(@"Responses\SQL GetSchema ProductModel.json", @"Responses\SQL GetRelationships SampleDB.json");
|
||||
b = sqlTable.TabularRecordType.TryGetFieldType("ProductModelID", out FormulaType productModelID);
|
||||
b = sqlTable.RecordType.TryGetFieldType("ProductModelID", out FormulaType productModelID);
|
||||
|
||||
Assert.True(b);
|
||||
CdpRecordType productModelRecordType = Assert.IsType<CdpRecordType>(productModelID);
|
||||
|
@ -262,7 +252,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
|
||||
// External relationship table name
|
||||
Assert.Equal("[SalesLT].[ProductModel]", productModelRecordType.TableSymbolName);
|
||||
Assert.Equal("![CatalogDescription:s, ModifiedDate:d, Name:s, ProductModelID:w, rowguid:s]", productModelRecordType.ToStringWithDisplayNames()); // Logical Name
|
||||
Assert.Equal<object>("r![CatalogDescription:s, ModifiedDate:d, Name:s, ProductModelID:~[SalesLT].[ProductModel]:w, rowguid:s]", productModelRecordType.ToStringWithDisplayNames()); // Logical Name
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -291,7 +281,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(sapTable.IsInitialized);
|
||||
|
||||
CdpTableValue sapTableValue = sapTable.GetTableValue();
|
||||
Assert.Equal<object>("*[ALL_EMPLOYEES:s, APP_MODE:s, BEGIN_DATE:s, BEGIN_DATE_CHAR:s, COMMAND:s, DESCRIPTION:s, EMP_PERNR:s, END_DATE:s, END_DATE_CHAR:s, EVENT_NAME:s, FLAG:s, GetMessages:*[MESSAGE:s, PERNR:s], HIDE_PEERS:s, LEGEND:s, LEGENDID:s, LEGEND_TEXT:s, PERNR:s, PERNR_MEM_ID:s, TYPE:s]", sapTableValue.Type._type.ToString());
|
||||
Assert.Equal<object>(
|
||||
"r*[ALL_EMPLOYEES:s, APP_MODE:s, BEGIN_DATE:s, BEGIN_DATE_CHAR:s, COMMAND:s, DESCRIPTION:s, EMP_PERNR:s, END_DATE:s, END_DATE_CHAR:s, EVENT_NAME:s, FLAG:s, GetMessages:*[MESSAGE:s, PERNR:s], " +
|
||||
"HIDE_PEERS:s, LEGEND:s, LEGENDID:s, LEGEND_TEXT:s, PERNR:s, PERNR_MEM_ID:s, TYPE:s]", sapTableValue.Type.ToStringWithDisplayNames());
|
||||
|
||||
string expr = "First(TeamCalendarCollection).LEGEND_TEXT";
|
||||
|
||||
|
@ -341,7 +333,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
CdpTableValue sqlTable = tabularService.GetTableValue();
|
||||
Assert.True(sqlTable._tabularService.IsInitialized);
|
||||
Assert.True(sqlTable.IsDelegable);
|
||||
Assert.Equal("*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type._type.ToString());
|
||||
Assert.Equal<object>("r*[Address:s, Country:s, CustomerId:w, Name:s, Phone:s]", sqlTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Customers", sqlTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -418,13 +410,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(spTable._tabularService.IsInitialized);
|
||||
Assert.True(spTable.IsDelegable);
|
||||
|
||||
Assert.Equal(
|
||||
"*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ComplianceAssetId`'Compliance " +
|
||||
"Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color Tag':s, OData__DisplayName`Sensitivity:s, " +
|
||||
"OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtension}'`'File name with extension':s, '{FullPath}'`'Full " +
|
||||
"Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'Comments associated with the content approval of this " +
|
||||
"list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window " +
|
||||
"End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames());
|
||||
Assert.Equal<object>(
|
||||
"r*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, " +
|
||||
"Picture:s], ComplianceAssetId`'Compliance Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color" +
|
||||
" Tag':s, OData__DisplayName`Sensitivity:s, OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtensi" +
|
||||
"on}'`'File name with extension':s, '{FullPath}'`'Full Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'" +
|
||||
"Comments associated with the content approval of this list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, " +
|
||||
"Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
HashSet<IExternalTabularDataSource> ads = spTable.Type._type.AssociatedDataSources;
|
||||
Assert.NotNull(ads);
|
||||
|
@ -455,13 +447,11 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(tds.RequiresAsync);
|
||||
Assert.NotNull(tds.ServiceCapabilities);
|
||||
#endif
|
||||
|
||||
Assert.NotNull(spTable._connectorType);
|
||||
Assert.NotNull(spTable._connectorType.Relationships);
|
||||
Assert.Equal(3, spTable._connectorType.Relationships.Count);
|
||||
Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => kvp.Key)));
|
||||
Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => kvp.Value.TargetEntity)));
|
||||
Assert.Equal("Editor#Claims-Claims, Author#Claims-Claims, CheckoutUser#Claims-Claims", string.Join(", ", spTable._connectorType.Relationships.Select(kvp => string.Join("|", kvp.Value.ReferentialConstraints.Select(kvp2 => $"{kvp2.Key}-{kvp2.Value}")))));
|
||||
Assert.NotNull(spTable.Relationships);
|
||||
Assert.Equal(3, spTable.Relationships.Count);
|
||||
Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable.Relationships.Select(kvp => kvp.Key)));
|
||||
Assert.Equal("Editor, Author, CheckoutUser", string.Join(", ", spTable.Relationships.Select(kvp => kvp.Value.TargetEntity)));
|
||||
Assert.Equal("Editor#Claims-Claims, Author#Claims-Claims, CheckoutUser#Claims-Claims", string.Join(", ", spTable.Relationships.Select(kvp => string.Join("|", kvp.Value.ReferentialConstraints.Select(kvp2 => $"{kvp2.Key}-{kvp2.Value}")))));
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Documents", spTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -508,13 +498,13 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(spTable._tabularService.IsInitialized);
|
||||
Assert.True(spTable.IsDelegable);
|
||||
|
||||
Assert.Equal(
|
||||
"*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ComplianceAssetId`'Compliance " +
|
||||
"Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color Tag':s, OData__DisplayName`Sensitivity:s, " +
|
||||
"OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtension}'`'File name with extension':s, '{FullPath}'`'Full " +
|
||||
"Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'Comments associated with the content approval of this " +
|
||||
"list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window " +
|
||||
"End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames());
|
||||
Assert.Equal<object>(
|
||||
"r*[Author`'Created By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], CheckoutUser`'Checked Out To':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, " +
|
||||
"Picture:s], ComplianceAssetId`'Compliance Asset Id':s, Created:d, Editor`'Modified By':![Claims:s, Department:s, DisplayName:s, Email:s, JobTitle:s, Picture:s], ID:w, Modified:d, OData__ColorTag`'Color" +
|
||||
" Tag':s, OData__DisplayName`Sensitivity:s, OData__ExtendedDescription`Description:s, OData__ip_UnifiedCompliancePolicyProperties`'Unified Compliance Policy Properties':s, Title:s, '{FilenameWithExtensi" +
|
||||
"on}'`'File name with extension':s, '{FullPath}'`'Full Path':s, '{Identifier}'`Identifier:s, '{IsCheckedOut}'`'Checked out':b, '{IsFolder}'`IsFolder:b, '{Link}'`'Link to item':s, '{ModerationComment}'`'" +
|
||||
"Comments associated with the content approval of this list item':s, '{ModerationStatus}'`'Content approval status':s, '{Name}'`Name:s, '{Path}'`'Folder path':s, '{Thumbnail}'`Thumbnail:![Large:s, " +
|
||||
"Medium:s, Small:s], '{TriggerWindowEndToken}'`'Trigger Window End Token':s, '{TriggerWindowStartToken}'`'Trigger Window Start Token':s, '{VersionNumber}'`'Version number':s]", spTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Documents", spTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -660,22 +650,22 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
// MasterRecordId`'Master Record ID'[Account]:s
|
||||
// OwnerId`'Owner ID'[User]:s
|
||||
// ParentId`'Parent Account ID'[Account]:s
|
||||
Assert.Equal(
|
||||
"![AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " +
|
||||
"Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID'[User]:s, CreatedDate`'Created Date':d, " +
|
||||
// Note 2: ~ notation denotes a relationship. Ex: fieldname`displayname:~externaltable:type
|
||||
Assert.Equal<object>(
|
||||
"r![AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " +
|
||||
"Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID'[User]:~User:s, CreatedDate`'Created Date':d, " +
|
||||
"Description`'Account Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last " +
|
||||
"Modified By ID'[User]:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID'[Account]:s, Name`'Account " +
|
||||
"Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID'[User]:s, ParentId`'Parent Account ID'[Account]:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping " +
|
||||
"Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping " +
|
||||
"State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", ((CdpRecordType)sfTable.TabularRecordType).ToStringWithDisplayNames());
|
||||
"Modified By ID'[User]:~User:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID'[Account]:~Account:s, " +
|
||||
"Name`'Account Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID'[User]:~User:s, ParentId`'Parent Account ID'[Account]:~Account:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping " +
|
||||
"City':s, ShippingCountry`'Shipping Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping " +
|
||||
"Zip/Postal Code':s, ShippingState`'Shipping State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]",
|
||||
((CdpRecordType)sfTable.RecordType).ToStringWithDisplayNames());
|
||||
|
||||
Assert.Equal("Account", sfTable.TabularRecordType.TableSymbolName);
|
||||
Assert.Equal("Account", sfTable.RecordType.TableSymbolName);
|
||||
|
||||
RecordType rt = sfTable.TabularRecordType;
|
||||
NamedFormulaType nft = rt.GetFieldTypes().First();
|
||||
RecordType rt = sfTable.RecordType;
|
||||
|
||||
Assert.Equal("AccountSource", nft.Name);
|
||||
Assert.Equal("Account Source", nft.DisplayName);
|
||||
Assert.True(rt.TryGetUnderlyingFieldType("AccountSource", out FormulaType ft));
|
||||
|
||||
HashSet<IExternalTabularDataSource> ads = sfTable.Type._type.AssociatedDataSources;
|
||||
Assert.NotNull(ads);
|
||||
|
@ -707,73 +697,76 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.NotNull(tds.ServiceCapabilities);
|
||||
#endif
|
||||
|
||||
Assert.NotNull(sfTable._connectorType);
|
||||
|
||||
// SF doesn't use x-ms-releationships extension
|
||||
Assert.Null(sfTable._connectorType.Relationships);
|
||||
Assert.Null(sfTable.Relationships);
|
||||
|
||||
// needs Microsoft.PowerFx.Connectors.CdpExtensions
|
||||
// this call does not make any network call
|
||||
bool b = sfTable.TabularRecordType.TryGetFieldExternalTableName("OwnerId", out string externalTableName, out string foreignKey);
|
||||
bool b = sfTable.RecordType.TryGetFieldExternalTableName("OwnerId", out string externalTableName, out string foreignKey);
|
||||
Assert.True(b);
|
||||
Assert.Equal("User", externalTableName);
|
||||
Assert.Null(foreignKey); // Always the case with SalesForce
|
||||
|
||||
testConnector.SetResponseFromFile(@"Responses\SF GetSchema Users.json");
|
||||
b = sfTable.TabularRecordType.TryGetFieldType("OwnerId", out FormulaType ownerIdType);
|
||||
b = sfTable.RecordType.TryGetFieldType("OwnerId", out FormulaType ownerIdType);
|
||||
|
||||
Assert.True(b);
|
||||
CdpRecordType userTable = Assert.IsType<CdpRecordType>(ownerIdType);
|
||||
|
||||
Assert.False((CdpRecordType)sfTable.TabularRecordType is null);
|
||||
Assert.False((CdpRecordType)sfTable.RecordType is null);
|
||||
Assert.False(userTable is null);
|
||||
|
||||
// External relationship table name
|
||||
Assert.Equal("User", userTable.TableSymbolName);
|
||||
|
||||
Assert.Equal(
|
||||
"![AboutMe`'About Me':s, AccountId`'Account ID'[Account]:s, Alias:s, BadgeText`'User Photo badge text overlay':s, BannerPhotoUrl`'Url for banner photo':s, CallCenterId`'Call Center ID':s, City:s, CommunityNickname`Nickname:s, " +
|
||||
"CompanyName`'Company Name':s, ContactId`'Contact ID'[Contact]:s, Country:s, CreatedById`'Created By ID'[User]:s, CreatedDate`'Created Date':d, DefaultGroupNotificationFrequency`'Default Notification Frequency " +
|
||||
"when Joining Groups':l, DelegatedApproverId`'Delegated Approver ID':s, Department:s, DigestFrequency`'Chatter Email Highlights Frequency':l, Division:s, Email:s, EmailEncodingKey`'Email Encoding':l, EmailPreferencesAutoBcc`AutoBcc:b, " +
|
||||
"EmailPreferencesAutoBccStayInTouch`AutoBccStayInTouch:b, EmailPreferencesStayInTouchReminder`StayInTouchReminder:b, EmployeeNumber`'Employee Number':s, Extension:s, Fax:s, FederationIdentifier`'SAML Federation " +
|
||||
"ID':s, FirstName`'First Name':s, ForecastEnabled`'Allow Forecasting':b, FullPhotoUrl`'Url for full-sized Photo':s, GeocodeAccuracy`'Geocode Accuracy':l, Id`'User ID':s, IsActive`Active:b, IsExtIndicatorVisible`'Show " +
|
||||
"external indicator':b, IsProfilePhotoActive`'Has Profile Photo':b, LanguageLocaleKey`Language:l, LastLoginDate`'Last Login':d, LastModifiedById`'Last Modified By ID'[User]:s, LastModifiedDate`'Last Modified " +
|
||||
"Date':d, LastName`'Last Name':s, LastPasswordChangeDate`'Last Password Change or Reset':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, Latitude:w, LocaleSidKey`Locale:l, " +
|
||||
"Longitude:w, ManagerId`'Manager ID'[User]:s, MediumBannerPhotoUrl`'Url for Android banner photo':s, MediumPhotoUrl`'Url for medium profile photo':s, MiddleName`'Middle Name':s, MobilePhone`Mobile:s, Name`'Full " +
|
||||
"Name':s, OfflinePdaTrialExpirationDate`'Sales Anywhere Trial Expiration Date':d, OfflineTrialExpirationDate`'Offline Edition Trial Expiration Date':d, OutOfOfficeMessage`'Out of office message':s, Phone:s, " +
|
||||
"PostalCode`'Zip/Postal Code':s, ProfileId`'Profile ID'[Profile]:s, ReceivesAdminInfoEmails`'Admin Info Emails':b, ReceivesInfoEmails`'Info Emails':b, SenderEmail`'Email Sender Address':s, SenderName`'Email " +
|
||||
"Sender Name':s, Signature`'Email Signature':s, SmallBannerPhotoUrl`'Url for IOS banner photo':s, SmallPhotoUrl`Photo:s, State`'State/Province':s, StayInTouchNote`'Stay-in-Touch Email Note':s, StayInTouchSignature`'Stay-in-Touch " +
|
||||
"Email Signature':s, StayInTouchSubject`'Stay-in-Touch Email Subject':s, Street:s, Suffix:s, SystemModstamp`'System Modstamp':d, TimeZoneSidKey`'Time Zone':l, Title:s, UserPermissionsAvantgoUser`'AvantGo " +
|
||||
"User':b, UserPermissionsCallCenterAutoLogin`'Auto-login To Call Center':b, UserPermissionsInteractionUser`'Flow User':b, UserPermissionsKnowledgeUser`'Knowledge User':b, UserPermissionsLiveAgentUser`'Chat " +
|
||||
"User':b, UserPermissionsMarketingUser`'Marketing User':b, UserPermissionsMobileUser`'Apex Mobile User':b, UserPermissionsOfflineUser`'Offline User':b, UserPermissionsSFContentUser`'Salesforce CRM Content " +
|
||||
"User':b, UserPermissionsSupportUser`'Service Cloud User':b, UserPreferencesActivityRemindersPopup`ActivityRemindersPopup:b, UserPreferencesApexPagesDeveloperMode`ApexPagesDeveloperMode:b, UserPreferencesCacheDiagnostics`CacheDiagnostics:b, " +
|
||||
"UserPreferencesCreateLEXAppsWTShown`CreateLEXAppsWTShown:b, UserPreferencesDisCommentAfterLikeEmail`DisCommentAfterLikeEmail:b, UserPreferencesDisMentionsCommentEmail`DisMentionsCommentEmail:b, UserPreferencesDisProfPostCommentEmail`DisProfP" +
|
||||
"ostCommentEmail:b, UserPreferencesDisableAllFeedsEmail`DisableAllFeedsEmail:b, UserPreferencesDisableBookmarkEmail`DisableBookmarkEmail:b, UserPreferencesDisableChangeCommentEmail`DisableChangeCommentEmail:b, " +
|
||||
"UserPreferencesDisableEndorsementEmail`DisableEndorsementEmail:b, UserPreferencesDisableFileShareNotificationsForApi`DisableFileShareNotificationsForApi:b, UserPreferencesDisableFollowersEmail`DisableFollowersEmail:b, " +
|
||||
"UserPreferencesDisableLaterCommentEmail`DisableLaterCommentEmail:b, UserPreferencesDisableLikeEmail`DisableLikeEmail:b, UserPreferencesDisableMentionsPostEmail`DisableMentionsPostEmail:b, UserPreferencesDisableMessageEmail`DisableMessageEmai" +
|
||||
"l:b, UserPreferencesDisableProfilePostEmail`DisableProfilePostEmail:b, UserPreferencesDisableSharePostEmail`DisableSharePostEmail:b, UserPreferencesEnableAutoSubForFeeds`EnableAutoSubForFeeds:b, UserPreferencesEventRemindersCheckboxDefault`E" +
|
||||
"ventRemindersCheckboxDefault:b, UserPreferencesExcludeMailAppAttachments`ExcludeMailAppAttachments:b, UserPreferencesFavoritesShowTopFavorites`FavoritesShowTopFavorites:b, UserPreferencesFavoritesWTShown`FavoritesWTShown:b, " +
|
||||
"UserPreferencesGlobalNavBarWTShown`GlobalNavBarWTShown:b, UserPreferencesGlobalNavGridMenuWTShown`GlobalNavGridMenuWTShown:b, UserPreferencesHideBiggerPhotoCallout`HideBiggerPhotoCallout:b, UserPreferencesHideCSNDesktopTask`HideCSNDesktopTas" +
|
||||
"k:b, UserPreferencesHideCSNGetChatterMobileTask`HideCSNGetChatterMobileTask:b, UserPreferencesHideChatterOnboardingSplash`HideChatterOnboardingSplash:b, UserPreferencesHideEndUserOnboardingAssistantModal`HideEndUserOnboardingAssistantModal:b" +
|
||||
", UserPreferencesHideLightningMigrationModal`HideLightningMigrationModal:b, UserPreferencesHideS1BrowserUI`HideS1BrowserUI:b, UserPreferencesHideSecondChatterOnboardingSplash`HideSecondChatterOnboardingSplash:b, " +
|
||||
"UserPreferencesHideSfxWelcomeMat`HideSfxWelcomeMat:b, UserPreferencesLightningExperiencePreferred`LightningExperiencePreferred:b, UserPreferencesPathAssistantCollapsed`PathAssistantCollapsed:b, UserPreferencesPreviewLightning`PreviewLightnin" +
|
||||
"g:b, UserPreferencesRecordHomeReservedWTShown`RecordHomeReservedWTShown:b, UserPreferencesRecordHomeSectionCollapseWTShown`RecordHomeSectionCollapseWTShown:b, UserPreferencesReminderSoundOff`ReminderSoundOff:b, " +
|
||||
"UserPreferencesShowCityToExternalUsers`ShowCityToExternalUsers:b, UserPreferencesShowCityToGuestUsers`ShowCityToGuestUsers:b, UserPreferencesShowCountryToExternalUsers`ShowCountryToExternalUsers:b, UserPreferencesShowCountryToGuestUsers`Show" +
|
||||
"CountryToGuestUsers:b, UserPreferencesShowEmailToExternalUsers`ShowEmailToExternalUsers:b, UserPreferencesShowEmailToGuestUsers`ShowEmailToGuestUsers:b, UserPreferencesShowFaxToExternalUsers`ShowFaxToExternalUsers:b, " +
|
||||
"UserPreferencesShowFaxToGuestUsers`ShowFaxToGuestUsers:b, UserPreferencesShowManagerToExternalUsers`ShowManagerToExternalUsers:b, UserPreferencesShowManagerToGuestUsers`ShowManagerToGuestUsers:b, UserPreferencesShowMobilePhoneToExternalUsers" +
|
||||
"`ShowMobilePhoneToExternalUsers:b, UserPreferencesShowMobilePhoneToGuestUsers`ShowMobilePhoneToGuestUsers:b, UserPreferencesShowPostalCodeToExternalUsers`ShowPostalCodeToExternalUsers:b, UserPreferencesShowPostalCodeToGuestUsers`ShowPostalCo" +
|
||||
"deToGuestUsers:b, UserPreferencesShowProfilePicToGuestUsers`ShowProfilePicToGuestUsers:b, UserPreferencesShowStateToExternalUsers`ShowStateToExternalUsers:b, UserPreferencesShowStateToGuestUsers`ShowStateToGuestUsers:b, " +
|
||||
"UserPreferencesShowStreetAddressToExternalUsers`ShowStreetAddressToExternalUsers:b, UserPreferencesShowStreetAddressToGuestUsers`ShowStreetAddressToGuestUsers:b, UserPreferencesShowTitleToExternalUsers`ShowTitleToExternalUsers:b, " +
|
||||
"UserPreferencesShowTitleToGuestUsers`ShowTitleToGuestUsers:b, UserPreferencesShowWorkPhoneToExternalUsers`ShowWorkPhoneToExternalUsers:b, UserPreferencesShowWorkPhoneToGuestUsers`ShowWorkPhoneToGuestUsers:b, " +
|
||||
"UserPreferencesSortFeedByComment`SortFeedByComment:b, UserPreferencesTaskRemindersCheckboxDefault`TaskRemindersCheckboxDefault:b, UserRoleId`'Role ID'[UserRole]:s, UserType`'User Type':l, Username:s]", userTable.ToStringWithDisplayNames());
|
||||
Assert.Equal<object>(
|
||||
"r![AboutMe`'About Me':s, AccountId`'Account ID'[Account]:~Account:s, Alias:s, BadgeText`'User Photo badge text overlay':s, BannerPhotoUrl`'Url for banner photo':s, CallCenterId`'Call " +
|
||||
"Center ID':s, City:s, CommunityNickname`Nickname:s, CompanyName`'Company Name':s, ContactId`'Contact ID'[Contact]:~Contact:s, Country:s, CreatedById`'Created By ID'[User]:~User:s, CreatedDate`'Created " +
|
||||
"Date':d, DefaultGroupNotificationFrequency`'Default Notification Frequency when Joining Groups':l, DelegatedApproverId`'Delegated Approver ID':s, Department:s, DigestFrequency`'Chatter " +
|
||||
"Email Highlights Frequency':l, Division:s, Email:s, EmailEncodingKey`'Email Encoding':l, EmailPreferencesAutoBcc`AutoBcc:b, EmailPreferencesAutoBccStayInTouch`AutoBccStayInTouch:b, " +
|
||||
"EmailPreferencesStayInTouchReminder`StayInTouchReminder:b, EmployeeNumber`'Employee Number':s, Extension:s, Fax:s, FederationIdentifier`'SAML Federation ID':s, FirstName`'First Name':s, " +
|
||||
"ForecastEnabled`'Allow Forecasting':b, FullPhotoUrl`'Url for full-sized Photo':s, GeocodeAccuracy`'Geocode Accuracy':l, Id`'User ID':s, IsActive`Active:b, IsExtIndicatorVisible`'Show " +
|
||||
"external indicator':b, IsProfilePhotoActive`'Has Profile Photo':b, LanguageLocaleKey`Language:l, LastLoginDate`'Last Login':d, LastModifiedById`'Last Modified By ID'[User]:~User:s, " +
|
||||
"LastModifiedDate`'Last Modified Date':d, LastName`'Last Name':s, LastPasswordChangeDate`'Last Password Change or Reset':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last " +
|
||||
"Viewed Date':d, Latitude:w, LocaleSidKey`Locale:l, Longitude:w, ManagerId`'Manager ID'[User]:~User:s, MediumBannerPhotoUrl`'Url for Android banner photo':s, MediumPhotoUrl`'Url for " +
|
||||
"medium profile photo':s, MiddleName`'Middle Name':s, MobilePhone`Mobile:s, Name`'Full Name':s, OfflinePdaTrialExpirationDate`'Sales Anywhere Trial Expiration Date':d, OfflineTrialExpirationDate`'Offlin" +
|
||||
"e Edition Trial Expiration Date':d, OutOfOfficeMessage`'Out of office message':s, Phone:s, PostalCode`'Zip/Postal Code':s, ProfileId`'Profile ID'[Profile]:~Profile:s, ReceivesAdminInfoEmails`'Admin " +
|
||||
"Info Emails':b, ReceivesInfoEmails`'Info Emails':b, SenderEmail`'Email Sender Address':s, SenderName`'Email Sender Name':s, Signature`'Email Signature':s, SmallBannerPhotoUrl`'Url for " +
|
||||
"IOS banner photo':s, SmallPhotoUrl`Photo:s, State`'State/Province':s, StayInTouchNote`'Stay-in-Touch Email Note':s, StayInTouchSignature`'Stay-in-Touch Email Signature':s, StayInTouchSubject`'Stay-in-T" +
|
||||
"ouch Email Subject':s, Street:s, Suffix:s, SystemModstamp`'System Modstamp':d, TimeZoneSidKey`'Time Zone':l, Title:s, UserPermissionsAvantgoUser`'AvantGo User':b, UserPermissionsCallCenterAutoLogin`'Au" +
|
||||
"to-login To Call Center':b, UserPermissionsInteractionUser`'Flow User':b, UserPermissionsKnowledgeUser`'Knowledge User':b, UserPermissionsLiveAgentUser`'Chat User':b, UserPermissionsMarketingUser`'Mark" +
|
||||
"eting User':b, UserPermissionsMobileUser`'Apex Mobile User':b, UserPermissionsOfflineUser`'Offline User':b, UserPermissionsSFContentUser`'Salesforce CRM Content User':b, UserPermissionsSupportUser`'Ser" +
|
||||
"vice Cloud User':b, UserPreferencesActivityRemindersPopup`ActivityRemindersPopup:b, UserPreferencesApexPagesDeveloperMode`ApexPagesDeveloperMode:b, UserPreferencesCacheDiagnostics`CacheDiagnostics:b, " +
|
||||
"UserPreferencesCreateLEXAppsWTShown`CreateLEXAppsWTShown:b, UserPreferencesDisCommentAfterLikeEmail`DisCommentAfterLikeEmail:b, UserPreferencesDisMentionsCommentEmail`DisMentionsCommentEmail:b, " +
|
||||
"UserPreferencesDisProfPostCommentEmail`DisProfPostCommentEmail:b, UserPreferencesDisableAllFeedsEmail`DisableAllFeedsEmail:b, UserPreferencesDisableBookmarkEmail`DisableBookmarkEmail:b, " +
|
||||
"UserPreferencesDisableChangeCommentEmail`DisableChangeCommentEmail:b, UserPreferencesDisableEndorsementEmail`DisableEndorsementEmail:b, UserPreferencesDisableFileShareNotificationsForApi`DisableFileSha" +
|
||||
"reNotificationsForApi:b, UserPreferencesDisableFollowersEmail`DisableFollowersEmail:b, UserPreferencesDisableLaterCommentEmail`DisableLaterCommentEmail:b, UserPreferencesDisableLikeEmail`DisableLikeEma" +
|
||||
"il:b, UserPreferencesDisableMentionsPostEmail`DisableMentionsPostEmail:b, UserPreferencesDisableMessageEmail`DisableMessageEmail:b, UserPreferencesDisableProfilePostEmail`DisableProfilePostEmail:b, " +
|
||||
"UserPreferencesDisableSharePostEmail`DisableSharePostEmail:b, UserPreferencesEnableAutoSubForFeeds`EnableAutoSubForFeeds:b, UserPreferencesEventRemindersCheckboxDefault`EventRemindersCheckboxDefault:b," +
|
||||
" UserPreferencesExcludeMailAppAttachments`ExcludeMailAppAttachments:b, UserPreferencesFavoritesShowTopFavorites`FavoritesShowTopFavorites:b, UserPreferencesFavoritesWTShown`FavoritesWTShown:b, " +
|
||||
"UserPreferencesGlobalNavBarWTShown`GlobalNavBarWTShown:b, UserPreferencesGlobalNavGridMenuWTShown`GlobalNavGridMenuWTShown:b, UserPreferencesHideBiggerPhotoCallout`HideBiggerPhotoCallout:b, " +
|
||||
"UserPreferencesHideCSNDesktopTask`HideCSNDesktopTask:b, UserPreferencesHideCSNGetChatterMobileTask`HideCSNGetChatterMobileTask:b, UserPreferencesHideChatterOnboardingSplash`HideChatterOnboardingSplash:" +
|
||||
"b, UserPreferencesHideEndUserOnboardingAssistantModal`HideEndUserOnboardingAssistantModal:b, UserPreferencesHideLightningMigrationModal`HideLightningMigrationModal:b, UserPreferencesHideS1BrowserUI`Hid" +
|
||||
"eS1BrowserUI:b, UserPreferencesHideSecondChatterOnboardingSplash`HideSecondChatterOnboardingSplash:b, UserPreferencesHideSfxWelcomeMat`HideSfxWelcomeMat:b, UserPreferencesLightningExperiencePreferred`L" +
|
||||
"ightningExperiencePreferred:b, UserPreferencesPathAssistantCollapsed`PathAssistantCollapsed:b, UserPreferencesPreviewLightning`PreviewLightning:b, UserPreferencesRecordHomeReservedWTShown`RecordHomeRes" +
|
||||
"ervedWTShown:b, UserPreferencesRecordHomeSectionCollapseWTShown`RecordHomeSectionCollapseWTShown:b, UserPreferencesReminderSoundOff`ReminderSoundOff:b, UserPreferencesShowCityToExternalUsers`ShowCityTo" +
|
||||
"ExternalUsers:b, UserPreferencesShowCityToGuestUsers`ShowCityToGuestUsers:b, UserPreferencesShowCountryToExternalUsers`ShowCountryToExternalUsers:b, UserPreferencesShowCountryToGuestUsers`ShowCountryTo" +
|
||||
"GuestUsers:b, UserPreferencesShowEmailToExternalUsers`ShowEmailToExternalUsers:b, UserPreferencesShowEmailToGuestUsers`ShowEmailToGuestUsers:b, UserPreferencesShowFaxToExternalUsers`ShowFaxToExternalUs" +
|
||||
"ers:b, UserPreferencesShowFaxToGuestUsers`ShowFaxToGuestUsers:b, UserPreferencesShowManagerToExternalUsers`ShowManagerToExternalUsers:b, UserPreferencesShowManagerToGuestUsers`ShowManagerToGuestUsers:b" +
|
||||
", UserPreferencesShowMobilePhoneToExternalUsers`ShowMobilePhoneToExternalUsers:b, UserPreferencesShowMobilePhoneToGuestUsers`ShowMobilePhoneToGuestUsers:b, UserPreferencesShowPostalCodeToExternalUsers`" +
|
||||
"ShowPostalCodeToExternalUsers:b, UserPreferencesShowPostalCodeToGuestUsers`ShowPostalCodeToGuestUsers:b, UserPreferencesShowProfilePicToGuestUsers`ShowProfilePicToGuestUsers:b, UserPreferencesShowState" +
|
||||
"ToExternalUsers`ShowStateToExternalUsers:b, UserPreferencesShowStateToGuestUsers`ShowStateToGuestUsers:b, UserPreferencesShowStreetAddressToExternalUsers`ShowStreetAddressToExternalUsers:b, " +
|
||||
"UserPreferencesShowStreetAddressToGuestUsers`ShowStreetAddressToGuestUsers:b, UserPreferencesShowTitleToExternalUsers`ShowTitleToExternalUsers:b, UserPreferencesShowTitleToGuestUsers`ShowTitleToGuestUs" +
|
||||
"ers:b, UserPreferencesShowWorkPhoneToExternalUsers`ShowWorkPhoneToExternalUsers:b, UserPreferencesShowWorkPhoneToGuestUsers`ShowWorkPhoneToGuestUsers:b, UserPreferencesSortFeedByComment`SortFeedByComme" +
|
||||
"nt:b, UserPreferencesTaskRemindersCheckboxDefault`TaskRemindersCheckboxDefault:b, UserRoleId`'Role ID'[UserRole]:~UserRole:s, UserType`'User Type':l, Username:s]", userTable.ToStringWithDisplayNames());
|
||||
|
||||
// Missing field
|
||||
b = sfTable.TabularRecordType.TryGetFieldType("XYZ", out FormulaType xyzType);
|
||||
b = sfTable.RecordType.TryGetFieldType("XYZ", out FormulaType xyzType);
|
||||
Assert.False(b);
|
||||
Assert.Null(xyzType);
|
||||
|
||||
// Field with no relationship
|
||||
b = sfTable.TabularRecordType.TryGetFieldType("BillingCountry", out FormulaType billingCountryType);
|
||||
b = sfTable.RecordType.TryGetFieldType("BillingCountry", out FormulaType billingCountryType);
|
||||
Assert.True(b);
|
||||
Assert.Equal("s", billingCountryType._type.ToString());
|
||||
|
||||
|
@ -822,14 +815,14 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(sfTable._tabularService.IsInitialized);
|
||||
Assert.True(sfTable.IsDelegable);
|
||||
|
||||
Assert.Equal(
|
||||
"*[AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " +
|
||||
"Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID':s, CreatedDate`'Created Date':d, Description`'Account " +
|
||||
"Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last Modified By " +
|
||||
"ID':s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID':s, Name`'Account Name':s, NumberOfEmployees`Employees:w, " +
|
||||
"OwnerId`'Owner ID':s, ParentId`'Parent Account ID':s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping Country':s, ShippingGeocodeAccuracy`'Shipping " +
|
||||
"Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping State/Province':s, ShippingStreet`'Shipping " +
|
||||
"Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", sfTable.Type.ToStringWithDisplayNames());
|
||||
Assert.Equal<object>(
|
||||
"r*[AccountSource`'Account Source':l, BillingCity`'Billing City':s, BillingCountry`'Billing Country':s, BillingGeocodeAccuracy`'Billing Geocode Accuracy':l, BillingLatitude`'Billing Latitude':w, BillingLongitude`'Billing " +
|
||||
"Longitude':w, BillingPostalCode`'Billing Zip/Postal Code':s, BillingState`'Billing State/Province':s, BillingStreet`'Billing Street':s, CreatedById`'Created By ID':~User:s, CreatedDate`'Created Date':d, " +
|
||||
"Description`'Account Description':s, Id`'Account ID':s, Industry:l, IsDeleted`Deleted:b, Jigsaw`'Data.com Key':s, JigsawCompanyId`'Jigsaw Company ID':s, LastActivityDate`'Last Activity':D, LastModifiedById`'Last " +
|
||||
"Modified By ID':~User:s, LastModifiedDate`'Last Modified Date':d, LastReferencedDate`'Last Referenced Date':d, LastViewedDate`'Last Viewed Date':d, MasterRecordId`'Master Record ID':~Account:s, Name`'Account " +
|
||||
"Name':s, NumberOfEmployees`Employees:w, OwnerId`'Owner ID':~User:s, ParentId`'Parent Account ID':~Account:s, Phone`'Account Phone':s, PhotoUrl`'Photo URL':s, ShippingCity`'Shipping City':s, ShippingCountry`'Shipping " +
|
||||
"Country':s, ShippingGeocodeAccuracy`'Shipping Geocode Accuracy':l, ShippingLatitude`'Shipping Latitude':w, ShippingLongitude`'Shipping Longitude':w, ShippingPostalCode`'Shipping Zip/Postal Code':s, ShippingState`'Shipping " +
|
||||
"State/Province':s, ShippingStreet`'Shipping Street':s, SicDesc`'SIC Description':s, SystemModstamp`'System Modstamp':d, Type`'Account Type':l, Website:s]", sfTable.Type.ToStringWithDisplayNames());
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Accounts", sfTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -897,9 +890,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(zdTable._tabularService.IsInitialized);
|
||||
Assert.True(zdTable.IsDelegable);
|
||||
|
||||
Assert.Equal(
|
||||
"![active:b, alias:s, created_at:d, custom_role_id:w, details:s, email:s, external_id:s, id:w, last_login_at:d, locale:s, locale_id:w, moderator:b, name:s, notes:s, only_private_comments:b, organization_id:w, " +
|
||||
"phone:s, photo:s, restricted_agent:b, role:s, shared:b, shared_agent:b, signature:s, suspended:b, tags:s, ticket_restriction:s, time_zone:s, updated_at:d, url:s, user_fields:s, verified:b]", ((CdpRecordType)zdTable.TabularRecordType).ToStringWithDisplayNames());
|
||||
Assert.Equal(
|
||||
"r![active:b, alias:s, created_at:d, custom_role_id:w, details:s, email:s, external_id:s, id:w, last_login_at:d, locale:s, locale_id:w, moderator:b, name:s, notes:s, only_private_comments:b, organization_id:w, " +
|
||||
"phone:s, photo:s, restricted_agent:b, role:s, shared:b, shared_agent:b, signature:s, suspended:b, tags:s, ticket_restriction:s, time_zone:s, updated_at:d, url:s, user_fields:s, verified:b]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames());
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Users", zdTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
@ -968,9 +961,9 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.True(zdTable.IsDelegable);
|
||||
|
||||
Assert.Equal(
|
||||
"![assignee_id:w, brand_id:w, collaborator_ids:s, created_at:d, custom_fields:s, description:s, due_at:d, external_id:s, followup_ids:s, forum_topic_id:w, group_id:w, has_incidents:b, " +
|
||||
"r![assignee_id:w, brand_id:w, collaborator_ids:s, created_at:d, custom_fields:s, description:s, due_at:d, external_id:s, followup_ids:s, forum_topic_id:w, group_id:w, has_incidents:b, " +
|
||||
"id:w, organization_id:w, priority:l, problem_id:w, raw_subject:s, recipient:s, requester_id:w, satisfaction_rating:s, sharing_agreement_ids:s, status:s, subject:s, submitter_id:w, " +
|
||||
"tags:s, ticket_form_id:w, type:s, updated_at:d, url:s, via:s]", ((CdpRecordType)zdTable.TabularRecordType).ToStringWithDisplayNames());
|
||||
"tags:s, ticket_form_id:w, type:s, updated_at:d, url:s, via:s]", ((CdpRecordType)zdTable.RecordType).ToStringWithDisplayNames());
|
||||
|
||||
SymbolValues symbolValues = new SymbolValues().Add("Tickets", zdTable);
|
||||
RuntimeConfig rc = new RuntimeConfig(symbolValues).AddService<ConnectorLogger>(logger);
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
"x-ms-visibility": "important"
|
||||
},
|
||||
"priority": {
|
||||
"title": "priority",
|
||||
"title": "Priority",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"low",
|
||||
|
@ -37,7 +37,7 @@
|
|||
]
|
||||
},
|
||||
"priority2": {
|
||||
"title": "priority",
|
||||
"title": "Priority 2",
|
||||
"type": "string",
|
||||
"format": "enum",
|
||||
"enum": [
|
||||
|
|
|
@ -21,7 +21,8 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
var bugsFieldType = new HashSet<Type>();
|
||||
var bugNames = new HashSet<string>()
|
||||
{
|
||||
"ConnectorFunction._slash"
|
||||
"ConnectorFunction._slash",
|
||||
"ColumnCapabilities.DefaultFilterFunctionSupport"
|
||||
};
|
||||
|
||||
AnalyzeThreadSafety.CheckStatics(asm, bugsFieldType, bugNames);
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerFx.Core.Entities;
|
||||
using Microsoft.PowerFx.Core.Functions.Delegation;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Tests
|
||||
{
|
||||
public class DelegationTests
|
||||
{
|
||||
[Fact]
|
||||
public void CapabilityTest()
|
||||
{
|
||||
RecordType rt = new TestRecordType("myTable", RecordType.Empty().Add("logic", FormulaType.String, "display"), null);
|
||||
|
||||
Assert.NotNull(rt._type.AssociatedDataSources);
|
||||
Assert.NotEmpty(rt._type.AssociatedDataSources);
|
||||
|
||||
IExternalTabularDataSource externalDataSource = rt._type.AssociatedDataSources.First();
|
||||
|
||||
Assert.True(externalDataSource.IsDelegatable);
|
||||
|
||||
IDelegationMetadata delegationMetadata = externalDataSource.DelegationMetadata;
|
||||
|
||||
bool eq = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.Equal, DPath.Root.Append(new DName("logic")));
|
||||
bool neq = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.NotEqual, DPath.Root.Append(new DName("logic")));
|
||||
bool eq2 = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.Equal, DPath.Root.Append(new DName("logic2")));
|
||||
bool neq2 = delegationMetadata.FilterDelegationMetadata.IsBinaryOpInDelegationSupportedByColumn(BinaryOp.NotEqual, DPath.Root.Append(new DName("logic2")));
|
||||
|
||||
Assert.True(eq);
|
||||
Assert.False(neq);
|
||||
Assert.False(eq2);
|
||||
Assert.False(neq2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DelegationOperatorTest()
|
||||
{
|
||||
string enums = string.Join(", ", Enum.GetNames(typeof(DelegationOperator)).Select(name => name.ToLowerInvariant()).OrderBy(x => x));
|
||||
string constants = string.Join(", ", typeof(DelegationMetadataOperatorConstants).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).Select(fi => (string)fi.GetValue(null)).OrderBy(x => x));
|
||||
Assert.Equal<object>(enums, constants);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestRecordType : RecordType
|
||||
{
|
||||
private readonly RecordType _recordType;
|
||||
private readonly List<string> _allowedFilters;
|
||||
|
||||
public TestRecordType(string tableName, RecordType recordType, List<string> allowedFilters)
|
||||
: base(GetDisplayNameProvider(recordType), GetDelegationInfo(tableName, recordType))
|
||||
{
|
||||
_recordType = recordType;
|
||||
_allowedFilters = allowedFilters;
|
||||
}
|
||||
|
||||
public override bool TryGetFieldType(string fieldName, out FormulaType type)
|
||||
{
|
||||
return _recordType.TryGetFieldType(fieldName, out type);
|
||||
}
|
||||
|
||||
private static DisplayNameProvider GetDisplayNameProvider(RecordType recordType)
|
||||
{
|
||||
return DisplayNameProvider.New(recordType.FieldNames.Select(f => new KeyValuePair<DName, DName>(new DName(f), new DName(f))));
|
||||
}
|
||||
|
||||
private static TableDelegationInfo GetDelegationInfo(string tableName, RecordType recordType)
|
||||
{
|
||||
return new TestDelegationInfo(recordType)
|
||||
{
|
||||
TableName = tableName
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
if (other == null || other is not TestRecordType other2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _recordType == other2._recordType;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1065 // Exceptions should not be raised in this type of method.
|
||||
public override int GetHashCode() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1065
|
||||
}
|
||||
|
||||
public class TestDelegationInfo : TableDelegationInfo
|
||||
{
|
||||
private readonly RecordType _recordType;
|
||||
|
||||
public TestDelegationInfo(RecordType recordType)
|
||||
: base()
|
||||
{
|
||||
_recordType = recordType;
|
||||
}
|
||||
|
||||
public override bool IsDelegable => true;
|
||||
|
||||
public override ColumnCapabilitiesDefinition GetColumnCapability(string fieldName)
|
||||
{
|
||||
if (_recordType.TryGetFieldType(fieldName, out FormulaType ft))
|
||||
{
|
||||
return new ColumnCapabilitiesDefinition()
|
||||
{
|
||||
FilterFunctions = new List<DelegationOperator>() { DelegationOperator.Eq }
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,19 +36,14 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
}
|
||||
|
||||
public static string ToStringWithDisplayNames(this FormulaType ftype)
|
||||
{
|
||||
return ftype._type.ToStringWithDisplayNames();
|
||||
}
|
||||
|
||||
internal static string ToStringWithDisplayNames(this DType dtype)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendToWithDisplayNames(dtype);
|
||||
sb.AppendToWithDisplayNames(ftype._type, ftype);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
internal static string AppendToWithDisplayNames(this StringBuilder sb, DType dtype)
|
||||
{
|
||||
internal static string AppendToWithDisplayNames(this StringBuilder sb, DType dtype, FormulaType ftype)
|
||||
{
|
||||
sb.Append(DType.MapKindToStr(dtype.Kind));
|
||||
|
||||
switch (dtype.Kind)
|
||||
|
@ -57,6 +52,10 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
case DKind.Table:
|
||||
AppendAggregateType(sb, dtype.TypeTree, dtype.DisplayNameProvider);
|
||||
break;
|
||||
case DKind.LazyRecord:
|
||||
case DKind.LazyTable:
|
||||
AppendAggregateLazyType(sb, ftype as AggregateType ?? throw new InvalidOperationException("Not a valid type"));
|
||||
break;
|
||||
case DKind.OptionSet:
|
||||
case DKind.View:
|
||||
AppendOptionSetOrViewType(sb, dtype.TypeTree, dtype.DisplayNameProvider);
|
||||
|
@ -79,14 +78,14 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
sb.Append('(');
|
||||
bool first = true;
|
||||
|
||||
foreach (DName name in es.OptionNames)
|
||||
foreach (DName name in es.OptionNames.OrderBy(dn => dn.Value, StringComparer.Ordinal))
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(',');
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
first = false;
|
||||
|
||||
sb.Append(TexlLexer.EscapeName(name.Value));
|
||||
if (es.TryLookupValueByName(name.Value, out object value))
|
||||
|
@ -107,7 +106,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
sb.Append("[");
|
||||
|
||||
var strPre = string.Empty;
|
||||
foreach (var kvp in tree.GetPairs())
|
||||
foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
|
||||
{
|
||||
Contracts.Assert(kvp.Value.IsValid);
|
||||
sb.Append(strPre);
|
||||
|
@ -122,7 +121,71 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
}
|
||||
|
||||
sb.Append(":");
|
||||
sb.AppendToWithDisplayNames(kvp.Value);
|
||||
sb.AppendToWithDisplayNames(kvp.Value, null);
|
||||
strPre = ", ";
|
||||
}
|
||||
|
||||
sb.Append("]");
|
||||
}
|
||||
|
||||
private static void AppendAggregateLazyType(StringBuilder sb, AggregateType fType)
|
||||
{
|
||||
Contracts.AssertValue(sb);
|
||||
|
||||
sb.Append("[");
|
||||
|
||||
var strPre = string.Empty;
|
||||
foreach (string fieldName in fType.FieldNames.OrderBy(f => f, StringComparer.Ordinal))
|
||||
{
|
||||
sb.Append(strPre);
|
||||
sb.Append(TexlLexer.EscapeName(fieldName));
|
||||
|
||||
string display = fType._type.DisplayNameProvider.LogicalToDisplayPairs.FirstOrDefault(kvp => kvp.Key.Value == fieldName).Value.Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(display) && TexlLexer.EscapeName(display) != TexlLexer.EscapeName(fieldName))
|
||||
{
|
||||
sb.Append("`");
|
||||
sb.Append(TexlLexer.EscapeName(display));
|
||||
}
|
||||
|
||||
sb.Append(":");
|
||||
|
||||
IExternalTabularDataSource ads = fType._type.AssociatedDataSources.FirstOrDefault();
|
||||
DataSourceInfo dataSourceInfo = ads as DataSourceInfo;
|
||||
|
||||
if (dataSourceInfo == null && fType._type.TryGetType(new DName(fieldName), out DType type))
|
||||
{
|
||||
sb.Append(type.ToString());
|
||||
}
|
||||
else if (dataSourceInfo != null)
|
||||
{
|
||||
if (dataSourceInfo.ColumnsWithRelationships.TryGetValue(fieldName, out string remoteTable))
|
||||
{
|
||||
sb.Append('~');
|
||||
sb.Append(remoteTable);
|
||||
sb.Append(':');
|
||||
|
||||
if (!dataSourceInfo.RecordType.TryGetUnderlyingFieldType(fieldName, out FormulaType backingFieldType))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
sb.Append(backingFieldType._type.ToString());
|
||||
}
|
||||
else if (ads.Type.TryGetType(new DName(fieldName), out DType type2))
|
||||
{
|
||||
sb.Append(type2.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append('§');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append("§§");
|
||||
}
|
||||
|
||||
strPre = ", ";
|
||||
}
|
||||
|
||||
|
@ -136,7 +199,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
sb.Append("{");
|
||||
|
||||
var strPre = string.Empty;
|
||||
foreach (var kvp in tree.GetPairs())
|
||||
foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
|
||||
{
|
||||
Contracts.Assert(kvp.Value.IsValid);
|
||||
sb.Append(strPre);
|
||||
|
@ -151,7 +214,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
}
|
||||
|
||||
sb.Append(":");
|
||||
sb.AppendToWithDisplayNames(kvp.Value);
|
||||
sb.AppendToWithDisplayNames(kvp.Value, null);
|
||||
strPre = ", ";
|
||||
}
|
||||
|
||||
|
@ -166,7 +229,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
sb.Append("[");
|
||||
|
||||
var strPre = string.Empty;
|
||||
foreach (var kvp in tree.GetPairs())
|
||||
foreach (var kvp in tree.GetPairs().OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
|
||||
{
|
||||
Contracts.AssertNonEmpty(kvp.Key);
|
||||
Contracts.AssertValue(kvp.Value.Object);
|
||||
|
|
|
@ -47,7 +47,6 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
"Microsoft.PowerFx.ParseResult",
|
||||
"Microsoft.PowerFx.ParserOptions",
|
||||
"Microsoft.PowerFx.IPostCheckErrorHandler",
|
||||
|
||||
"Microsoft.PowerFx.EngineDocumentation",
|
||||
|
||||
// Config & Symbols
|
||||
|
@ -130,11 +129,11 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
"Microsoft.PowerFx.Types.DecimalValue",
|
||||
"Microsoft.PowerFx.Types.DeferredType",
|
||||
"Microsoft.PowerFx.Types.DelegationParameters",
|
||||
"Microsoft.PowerFx.Types.DelegationParameterFeatures",
|
||||
"Microsoft.PowerFx.Types.DelegationParameterFeatures",
|
||||
"Microsoft.PowerFx.Types.DValue`1",
|
||||
"Microsoft.PowerFx.Types.ErrorValue",
|
||||
"Microsoft.PowerFx.Types.ExternalType",
|
||||
"Microsoft.PowerFx.Types.ExternalTypeKind",
|
||||
"Microsoft.PowerFx.Types.ExternalTypeKind",
|
||||
"Microsoft.PowerFx.Types.FormulaType",
|
||||
"Microsoft.PowerFx.Types.FormulaValue",
|
||||
"Microsoft.PowerFx.Types.GuidType",
|
||||
|
@ -144,7 +143,7 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
"Microsoft.PowerFx.Types.ITypeVisitor",
|
||||
"Microsoft.PowerFx.Types.IUntypedObject",
|
||||
"Microsoft.PowerFx.Types.UntypedObjectBase",
|
||||
"Microsoft.PowerFx.Types.IValueVisitor",
|
||||
"Microsoft.PowerFx.Types.IValueVisitor",
|
||||
"Microsoft.PowerFx.Types.NamedFormulaType",
|
||||
"Microsoft.PowerFx.Types.NamedValue",
|
||||
"Microsoft.PowerFx.Types.NumberType",
|
||||
|
@ -193,18 +192,27 @@ namespace Microsoft.PowerFx.Core.Tests
|
|||
|
||||
// TBD ...
|
||||
"Microsoft.PowerFx.BasicUserInfo",
|
||||
"Microsoft.PowerFx.PowerFxFileInfo",
|
||||
"Microsoft.PowerFx.Core.DisplayNameProvider",
|
||||
"Microsoft.PowerFx.Core.DisplayNameUtility",
|
||||
"Microsoft.PowerFx.Core.Entities.IRefreshable",
|
||||
"Microsoft.PowerFx.Core.Entities.ColumnCapabilities",
|
||||
"Microsoft.PowerFx.Core.Entities.ColumnCapabilitiesBase",
|
||||
"Microsoft.PowerFx.Core.Entities.ColumnCapabilitiesDefinition",
|
||||
"Microsoft.PowerFx.Core.Entities.FilterRestrictions",
|
||||
"Microsoft.PowerFx.Core.Entities.GroupRestrictions",
|
||||
"Microsoft.PowerFx.Core.Entities.IRefreshable",
|
||||
"Microsoft.PowerFx.Core.Entities.SelectionRestrictions",
|
||||
"Microsoft.PowerFx.Core.Entities.SortRestrictions",
|
||||
"Microsoft.PowerFx.Core.Entities.TableDelegationInfo",
|
||||
"Microsoft.PowerFx.Core.Functions.Delegation.DelegationOperator",
|
||||
"Microsoft.PowerFx.Core.Localization.ErrorResourceKey",
|
||||
"Microsoft.PowerFx.Core.RenameDriver",
|
||||
"Microsoft.PowerFx.Core.Utils.DName",
|
||||
"Microsoft.PowerFx.Core.Utils.DPath",
|
||||
"Microsoft.PowerFx.Core.Utils.ICheckable",
|
||||
"Microsoft.PowerFx.UserInfo",
|
||||
"Microsoft.PowerFx.Logging.ITracer",
|
||||
"Microsoft.PowerFx.Logging.TraceSeverity"
|
||||
"Microsoft.PowerFx.Logging.TraceSeverity",
|
||||
"Microsoft.PowerFx.PowerFxFileInfo",
|
||||
"Microsoft.PowerFx.UserInfo"
|
||||
};
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
|
Загрузка…
Ссылка в новой задаче