Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple relationships in ConnectorType #2747

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -675,17 +675,16 @@ internal static ConnectorType GetConnectorType(this ISwaggerParameter openApiPar
// Here, we have a circular reference and default to a string
return new ConnectorType(schema, openApiParameter, FormulaType.String, hiddenfields.ToRecordType());
}

//ConnectorType propertyType = new OpenApiParameter() { Name = propLogicalName, Required = schema.Required.Contains(propLogicalName), Schema = kv.Value, Extensions = kv.Value.Extensions }.GetConnectorType(settings.Stack(schemaIdentifier));

ConnectorType propertyType = new SwaggerParameter(propLogicalName, schema.Required.Contains(propLogicalName), kv.Value, kv.Value.Extensions).GetConnectorType(settings.Stack(schemaIdentifier));

if (settings.SqlRelationships != null)
{
SqlRelationship relationship = settings.SqlRelationships.FirstOrDefault(sr => sr.ColumnName == propLogicalName);
IEnumerable<SqlRelationship> relationships = settings.SqlRelationships.Where(sr => sr.ColumnName == propLogicalName);

if (relationship != null)
if (relationships.Any())
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
{
propertyType.SetRelationship(relationship);
propertyType.SetRelationships(relationships);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public bool TryGetFieldExternalTableName(string fieldName, out string tableName,
return false;
}

tableName = connectorType.ExternalTables.First();
foreignKey = connectorType.ForeignKey;
// $$$ We should NOT call First() here but consider all possible foreign tables
(tableName, foreignKey) = connectorType.ExternalTables.First();
return true;
}

Expand All @@ -60,7 +60,8 @@ private bool TryGetFieldType(string fieldName, bool ignorelationship, out Formul
return true;
}

string tableName = field.ExternalTables.First();
// $$$ We should NOT call First() here but consider all possible foreign tables
string tableName = field.ExternalTables.First().foreignTable;

try
{
Expand Down
107 changes: 93 additions & 14 deletions src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace Microsoft.PowerFx.Connectors
// FormulaType is used to represent the type of the parameter in the Power Fx expression as used in Power Apps
// ConnectorType contains more details information coming from the swagger file and extensions
[DebuggerDisplay("{FormulaType._type}")]
public class ConnectorType : SupportsConnectorErrors
public class ConnectorType : SupportsConnectorErrors, IEquatable<ConnectorType>
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
{
// "name"
public string Name { get; internal set; }
Expand Down Expand Up @@ -112,11 +112,7 @@ public class ConnectorType : SupportsConnectorErrors
internal ISwaggerSchema Schema { get; private set; } = null;

// Relationships to external tables
internal List<string> ExternalTables { get; set; }

internal string RelationshipName { get; set; }
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved

internal string ForeignKey { get; set; }
internal List<(string foreignTable, string foreignKey)> ExternalTables { get; set; }
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved

internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter, FormulaType formulaType, ErrorResourceKey warning = default)
{
Expand Down Expand Up @@ -148,9 +144,7 @@ internal ConnectorType(ISwaggerSchema schema, ISwaggerParameter openApiParameter
// SalesForce only
if (schema.ReferenceTo != null && schema.ReferenceTo.Count == 1)
{
ExternalTables = new List<string>(schema.ReferenceTo);
RelationshipName = schema.RelationshipName;
ForeignKey = null; // SalesForce doesn't provide it, defaults to "Id"
ExternalTables = new List<(string, string)>(schema.ReferenceTo.Select(r => (r, null as string /* Seems to always be Id */)));
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
}

Fields = Array.Empty<ConnectorType>();
Expand Down Expand Up @@ -290,12 +284,14 @@ internal DisplayNameProvider DisplayNameProvider

private DisplayNameProvider _displayNameProvider;

internal void SetRelationship(SqlRelationship relationship)
internal void SetRelationships(IEnumerable<SqlRelationship> relationships)
{
ExternalTables ??= new List<string>();
ExternalTables.Add(relationship.ReferencedTable);
RelationshipName = relationship.RelationshipName;
ForeignKey = relationship.ReferencedColumnName;
ExternalTables ??= new List<(string, string)>();

foreach (SqlRelationship relationship in relationships)
{
ExternalTables.Add((relationship.ReferencedTable, relationship.ReferencedColumnName));
}
}

private void AggregateErrors(ConnectorType[] types)
Expand Down Expand Up @@ -336,5 +332,88 @@ private Dictionary<string, FormulaValue> GetEnum()

return enumDisplayNames.Zip(enumValues, (dn, ev) => new KeyValuePair<string, FormulaValue>(dn, ev)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

public bool Equals(ConnectorType other)
{
return this.Name == other.Name &&
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
this.DisplayName == other.DisplayName &&
this.Description == other.Description &&
this.IsRequired == other.IsRequired &&
Enumerable.SequenceEqual((IList<ConnectorType>)this.Fields ?? Array.Empty<ConnectorType>(), (IList<ConnectorType>)other.Fields ?? Array.Empty<ConnectorType>()) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(IList)this.Fields

This is recursive - how do we verify this is not a stack overflow?

Enumerable.SequenceEqual((IList<ConnectorType>)this.HiddenFields ?? Array.Empty<ConnectorType>(), (IList<ConnectorType>)other.HiddenFields ?? Array.Empty<ConnectorType>()) &&
this.ExplicitInput == other.ExplicitInput &&
this.IsEnum == other.IsEnum &&
Enumerable.SequenceEqual((IList<FormulaValue>)this.EnumValues ?? Array.Empty<FormulaValue>(), (IList<FormulaValue>)other.EnumValues ?? Array.Empty<FormulaValue>()) &&
Enumerable.SequenceEqual((IList<string>)this.EnumDisplayNames ?? Array.Empty<string>(), (IList<string>)other.EnumDisplayNames ?? Array.Empty<string>()) &&
this.Visibility == other.Visibility &&
((this.Capabilities == null && other.Capabilities == null) || this.Capabilities.Equals(other.Capabilities)) &&
this.KeyType == other.KeyType &&
this.KeyOrder == other.KeyOrder &&
this.Permission == other.Permission &&
this.NotificationUrl == other.NotificationUrl &&
((this.HiddenRecordType == null && other.HiddenRecordType == null) || this.HiddenRecordType.Equals(other.HiddenRecordType)) &&
this.Binary == other.Binary &&
this.MediaKind == other.MediaKind &&
Enumerable.SequenceEqual((IList<string>)this.ExternalTables ?? Array.Empty<string>(), (IList<string>)other.ExternalTables ?? Array.Empty<string>());
}

public override bool Equals(object obj)
{
return Equals(obj as ConnectorType);
}

public override int GetHashCode()
{
int h = Hashing.CombineHash(Name.GetHashCode(), DisplayName?.GetHashCode() ?? 0, Description?.GetHashCode() ?? 0, IsRequired.GetHashCode());

if (Fields != null)
{
foreach (ConnectorType field in Fields)
{
h = Hashing.CombineHash(h, field.GetHashCode());
}
}

if (HiddenFields != null)
{
foreach (ConnectorType hiddenField in HiddenFields)
{
h = Hashing.CombineHash(h, hiddenField.GetHashCode());
}
}

h = Hashing.CombineHash(h, ExplicitInput.GetHashCode(), IsEnum.GetHashCode(), Visibility.GetHashCode());

if (EnumValues != null)
{
foreach (FormulaValue enumValue in EnumValues)
{
h = Hashing.CombineHash(h, enumValue.GetHashCode());
}
}

if (EnumDisplayNames != null)
{
foreach (string enumDisplayName in EnumDisplayNames)
{
h = Hashing.CombineHash(h, enumDisplayName.GetHashCode());
}
}

h = Hashing.CombineHash(h, Capabilities.GetHashCode(), KeyType.GetHashCode(), KeyOrder.GetHashCode(), Permission.GetHashCode(), NotificationUrl?.GetHashCode() ?? 0, HiddenRecordType.GetHashCode());
h = Hashing.CombineHash(h, Binary.GetHashCode());
h = Hashing.CombineHash(h, MediaKind.GetHashCode());

if (ExternalTables != null)
{
foreach ((string externalTable, string foreignKey) in ExternalTables)
{
h = Hashing.CombineHash(h, externalTable.GetHashCode());
h = Hashing.CombineHash(h, foreignKey.GetHashCode());
}
}

return h;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
Expand All @@ -12,7 +13,7 @@

namespace Microsoft.PowerFx.Connectors
{
internal sealed class ColumnCapabilities : ColumnCapabilitiesBase, IColumnsCapabilities
internal sealed class ColumnCapabilities : ColumnCapabilitiesBase, IColumnsCapabilities, IEquatable<ColumnCapabilities>
{
[JsonInclude]
[JsonPropertyName(CapabilityConstants.ColumnProperties)]
Expand Down Expand Up @@ -72,5 +73,32 @@ public static ColumnCapabilities ParseColumnCapabilities(IDictionary<string, IOp
IsChoice = isChoice
});
}

public bool Equals(ColumnCapabilities other)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public bool Equals(ColumnCapabilities other)

Intrinsically, why would we ever need to compare ColumnCapabilities?

{
return Enumerable.SequenceEqual((IEnumerable<KeyValuePair<string, ColumnCapabilitiesBase>>)this._childColumnsCapabilities ?? Array.Empty<KeyValuePair<string, ColumnCapabilitiesBase>>(), (IEnumerable<KeyValuePair<string, ColumnCapabilitiesBase>>)other._childColumnsCapabilities ?? Array.Empty<KeyValuePair<string, ColumnCapabilitiesBase>>()) &&
this.Capabilities.Equals(other.Capabilities);
}

public override bool Equals(object obj)
{
return Equals(obj as ColumnCapabilities);
}

public override int GetHashCode()
{
int h = Capabilities.GetHashCode();

if (_childColumnsCapabilities != null)
{
foreach (KeyValuePair<string, ColumnCapabilitiesBase> kvp in _childColumnsCapabilities)
{
h = Hashing.CombineHash(h, kvp.Key.GetHashCode());
h = Hashing.CombineHash(h, kvp.Value.GetHashCode());
}
}

return h;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace Microsoft.PowerFx.Connectors
[JsonConverter(typeof(AbstractTypeConverter<ColumnCapabilitiesBase>))]
internal abstract class ColumnCapabilitiesBase
{
public abstract override int GetHashCode();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// 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.PowerFx.Core.Utils;

Expand All @@ -10,7 +12,7 @@

namespace Microsoft.PowerFx.Connectors
{
internal sealed class ColumnCapabilitiesDefinition
internal sealed class ColumnCapabilitiesDefinition : IEquatable<ColumnCapabilitiesDefinition>
{
[JsonInclude]
[JsonPropertyName(CapabilityConstants.FilterFunctions)]
Expand All @@ -33,5 +35,32 @@ 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;
}

public bool Equals(ColumnCapabilitiesDefinition other)
{
return Enumerable.SequenceEqual(this.FilterFunctions ?? Array.Empty<string>(), other.FilterFunctions ?? Array.Empty<string>()) &&
this.QueryAlias == other.QueryAlias &&
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
this.IsChoice == other.IsChoice;
}

public override bool Equals(object obj)
{
return Equals(obj as ColumnCapabilitiesDefinition);
}

public override int GetHashCode()
{
int h = Hashing.CombineHash(QueryAlias.GetHashCode(), IsChoice?.GetHashCode() ?? 0);

if (FilterFunctions != null)
{
foreach (var filterFunction in FilterFunctions)
{
h = Hashing.CombineHash(h, filterFunction.GetHashCode());
}
}

return h;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ public void AddColumnCapability(string name, ColumnCapabilitiesBase capability)

_childColumnsCapabilities.Add(name, capability);
}

public override int GetHashCode()
{
throw new System.NotImplementedException();
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ public static TableDelegationInfo ToDelegationInfo(ServiceCapabilities serviceCa
_ => 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);
// $$$ We should not use ExternalTables.First() but consider all relationships here
Dictionary<string, string> columnWithRelationships = connectorType.Fields.Where(f => f.ExternalTables?.Any() == true).Select(f => (f.Name, f.ExternalTables.First().foreignTable)).ToDictionary(tpl => tpl.Name, tpl => tpl.foreignTable);

return new CdpDelegationInfo()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ public override bool TryShallowCopy(out FormulaValue copy)
protected override bool TryGetField(FormulaType fieldType, string fieldName, out FormulaValue result)
{
if (Type.TryGetFieldType(fieldName, out var compileTimeType))
{
{
// Only return field which were specified via the expectedType (IE RecordType),
// because inner record value may have more fields than the expected type.
if (compileTimeType == fieldType && _fields.TryGetValue(fieldName, out result))
if (compileTimeType.Equals(fieldType) && _fields.TryGetValue(fieldName, out result))
LucGenetier marked this conversation as resolved.
Show resolved Hide resolved
{
return true;
}
Expand Down
Loading