protected DelegationCapability ParseColumnCapability(JsonElement columnCapabilityJsonObj, string capabilityKey) { Contracts.AssertNonEmpty(capabilityKey); // Retrieve the entry for the column using column name as key. if (!columnCapabilityJsonObj.TryGetProperty(capabilityKey, out var functionsJsonArray)) { return(DelegationCapability.None); } DelegationCapability columnCapability = DelegationCapability.None; foreach (var op in functionsJsonArray.EnumerateArray()) { var operatorStr = op.GetString(); Contracts.AssertNonEmpty(operatorStr); // If we don't support the operator then don't look at this capability. if (!DelegationCapability.OperatorToDelegationCapabilityMap.ContainsKey(operatorStr)) { continue; } columnCapability |= DelegationCapability.OperatorToDelegationCapabilityMap[operatorStr]; } return(columnCapability); }
public override bool IsDelegationSupportedByTable(DelegationCapability delegationCapability) { if (_filterFunctionsSupportedByTable.HasValue) { return(_filterFunctionsSupportedByTable.Value.HasCapability(delegationCapability.Capabilities)); } else { return(base.IsDelegationSupportedByTable(delegationCapability)); /* This is needed for compatibility with older metadata */ } }
public override OperationCapabilityMetadata Parse(JsonElement dataServiceCapabilitiesJsonObject, DType schema) { Contracts.AssertValid(schema); Dictionary <DPath, DelegationCapability> columnRestrictions = new Dictionary <DPath, DelegationCapability>(); if (!dataServiceCapabilitiesJsonObject.TryGetProperty(CapabilitiesConstants.Sort_Restriction, out var sortRestrictionJsonObject)) { return(null); } if (sortRestrictionJsonObject.TryGetProperty(CapabilitiesConstants.Sort_UnsortableProperties, out var unSortablePropertiesJsonArray)) { foreach (var prop in unSortablePropertiesJsonArray.EnumerateArray()) { var columnName = new DName(prop.GetString()); var columnPath = DPath.Root.Append(columnName); if (!columnRestrictions.ContainsKey(columnPath)) { columnRestrictions.Add(columnPath, new DelegationCapability(DelegationCapability.Sort)); } } } if (sortRestrictionJsonObject.TryGetProperty(CapabilitiesConstants.Sort_AscendingOnlyProperties, out var acendingOnlyPropertiesJsonArray)) { foreach (var prop in acendingOnlyPropertiesJsonArray.EnumerateArray()) { var columnName = new DName(prop.GetString()); var columnPath = DPath.Root.Append(columnName); if (!columnRestrictions.ContainsKey(columnPath)) { columnRestrictions.Add(columnPath, new DelegationCapability(DelegationCapability.SortAscendingOnly)); continue; } var existingRestrictions = columnRestrictions[columnPath].Capabilities; columnRestrictions[columnPath] = new DelegationCapability(existingRestrictions | DelegationCapability.SortAscendingOnly); } } return(new SortOpMetadata(schema, columnRestrictions)); }
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; } }
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)) { return(base.TryGetColumnCapabilities(columnPath, out capabilities)); } // If metadata specified any restrictions for this column then apply those // before returning capabilities. DelegationCapability restrictions; if (TryGetColumnRestrictions(columnPath, out restrictions)) { capabilities &= ~restrictions; } return(true); }
public override bool IsRowScopedServerDelegatable(CallNode callNode, TexlBinding binding, OperationCapabilityMetadata metadata) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); Contracts.AssertValue(metadata); if (binding.ErrorContainer.HasErrors(callNode) || !CheckArgsCount(callNode, binding) || !binding.IsRowScope(callNode)) { return(false); } TexlNode[] args = callNode.Args.Children.VerifyValue(); Contracts.Assert(args.Length >= MinArity); DelegationCapability funcDelegationCapability = FunctionDelegationCapability | (_isAnd ? DelegationCapability.And : DelegationCapability.Or); if (!metadata.IsDelegationSupportedByTable(funcDelegationCapability)) { return(false); } foreach (var arg in args) { NodeKind argKind = arg.VerifyValue().Kind; switch (argKind) { case NodeKind.FirstName: { var firstNameStrategy = GetFirstNameNodeDelegationStrategy(); if (!firstNameStrategy.IsValidFirstNameNode(arg.AsFirstName(), binding, null)) { return(false); } break; } case NodeKind.Call: { var cNodeStrategy = GetCallNodeDelegationStrategy(); if (!cNodeStrategy.IsValidCallNode(arg.AsCall(), binding, metadata)) { SuggestDelegationHint(arg, binding); return(false); } break; } case NodeKind.DottedName: { var dottedStrategy = GetDottedNameNodeDelegationStrategy(); if (!dottedStrategy.IsValidDottedNameNode(arg.AsDottedName(), binding, metadata, null)) { SuggestDelegationHint(arg, binding); return(false); } break; } case NodeKind.BinaryOp: { BinaryOpNode opNode = arg.AsBinaryOp(); var binaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op, opNode); if (!binaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding)) { SuggestDelegationHint(arg, binding); return(false); } break; } case NodeKind.UnaryOp: { UnaryOpNode opNode = arg.AsUnaryOpLit(); var unaryOpNodeValidationStrategy = GetOpDelegationStrategy(opNode.Op); if (!unaryOpNodeValidationStrategy.IsSupportedOpNode(opNode, metadata, binding)) { SuggestDelegationHint(arg, binding); return(false); } break; } case NodeKind.BoolLit: break; default: return(false); } } return(true); }
// See if CountDistinct delegation is available. If true, we can make use of it on primary key as a workaround for CountRows delegation internal bool TryGetValidDataSourceForDelegation(CallNode callNode, TexlBinding binding, out IExternalDataSource dataSource, out DelegationCapability preferredFunctionDelegationCapability) { Contracts.AssertValue(callNode); Contracts.AssertValue(binding); preferredFunctionDelegationCapability = FunctionDelegationCapability; // We ensure Document is available because some tests run with a null Document. if ((binding.Document != null && binding.Document.Properties.EnabledFeatures.IsEnhancedDelegationEnabled) && TryGetValidDataSourceForDelegation(callNode, binding, FunctionDelegationCapability, out dataSource) && !ExpressionContainsView(callNode, binding)) { // Check that target table is not an expanded entity (1-N/N-N relationships) // TASK 9966488: Enable CountRows/CountIf delegation for table relationships TexlNode[] args = callNode.Args.Children.VerifyValue(); if (args.Length > 0) { if (binding.GetType(args[0]).HasExpandInfo) { SuggestDelegationHint(callNode, binding); return(false); } else { return(true); } } } TryGetValidDataSourceForDelegation(callNode, binding, DelegationCapability.CountDistinct, out dataSource); if (dataSource != null && dataSource.IsDelegatable) { binding.ErrorContainer.EnsureError(DocumentErrorSeverity.Warning, callNode, TexlStrings.OpNotSupportedByServiceSuggestionMessage_OpNotSupportedByService, Name); } return(false); }
public MinMaxTableFunction(bool isMin) : base(isMin ? "Min" : "Max", isMin ? TexlStrings.AboutMinT : TexlStrings.AboutMaxT, FunctionCategories.Table) { _delegationCapability = isMin ? DelegationCapability.Min : DelegationCapability.Max; }
public override OperationCapabilityMetadata Parse(JsonElement dataServiceCapabilitiesJsonObject, DType schema) { Contracts.AssertValid(schema); // Check if any filter metadata is specified or not. var filterRestrictionExists = dataServiceCapabilitiesJsonObject.TryGetProperty(CapabilitiesConstants.Filter_Restriction, out var filterRestrictionJsonObject); var globalFilterFunctionsExists = dataServiceCapabilitiesJsonObject.TryGetProperty(CapabilitiesConstants.Filter_Functions, out var globalFilterFunctionsJsonArray); var globalFilterSupportsExists = dataServiceCapabilitiesJsonObject.TryGetProperty(CapabilitiesConstants.Filter_SupportedFunctions, out var globalFilterSupportedFunctionsJsonArray); var columnCapabilitiesExists = dataServiceCapabilitiesJsonObject.TryGetProperty(CapabilitiesConstants.ColumnsCapabilities, out var columnCapabilitiesJsonObj); if (!filterRestrictionExists && !globalFilterFunctionsExists && !globalFilterSupportsExists && !columnCapabilitiesExists) { return(null); } // Go through all filter restrictions if defined. var columnRestrictions = new Dictionary <DPath, DelegationCapability>(); // If any nonFilterablepropertis exist then mark each column as such. if (filterRestrictionExists && filterRestrictionJsonObject.TryGetProperty(CapabilitiesConstants.Filter_NonFilterableProperties, out var nonFilterablePropertiesJsonArray)) { foreach (var prop in nonFilterablePropertiesJsonArray.EnumerateArray()) { var columnName = DPath.Root.Append(new DName(prop.GetString())); if (!columnRestrictions.ContainsKey(columnName)) { columnRestrictions.Add(columnName, new DelegationCapability(DelegationCapability.Filter)); } } } // Check for any FilterFunctions defined at table level. DelegationCapability filterFunctionsSupportedByAllColumns = DelegationCapability.None; if (globalFilterFunctionsExists) { foreach (var op in globalFilterFunctionsJsonArray.EnumerateArray()) { var operatorStr = op.GetString(); Contracts.AssertNonEmpty(operatorStr); // If we don't support the operator then don't look at this capability. if (!DelegationCapability.OperatorToDelegationCapabilityMap.ContainsKey(operatorStr)) { continue; } // If filter functions are specified at table level then that means filter operation is supported. filterFunctionsSupportedByAllColumns |= DelegationCapability.OperatorToDelegationCapabilityMap[operatorStr] | DelegationCapability.Filter; } } // Check for any FilterSupportedFunctions defined at table level. DelegationCapability?filterFunctionsSupportedByTable = null; if (globalFilterSupportsExists) { filterFunctionsSupportedByTable = DelegationCapability.None; foreach (var op in globalFilterSupportedFunctionsJsonArray.EnumerateArray()) { var operatorStr = op.GetString(); Contracts.AssertNonEmpty(operatorStr); // If we don't support the operator then don't look at this capability. if (!DelegationCapability.OperatorToDelegationCapabilityMap.ContainsKey(operatorStr)) { continue; } // If filter functions are specified at table level then that means filter operation is supported. filterFunctionsSupportedByTable |= DelegationCapability.OperatorToDelegationCapabilityMap[operatorStr] | DelegationCapability.Filter; } } Dictionary <DPath, DelegationCapability> columnCapabilities = new Dictionary <DPath, DelegationCapability>(); if (!columnCapabilitiesExists) { return(new FilterOpMetadata(schema, columnRestrictions, columnCapabilities, filterFunctionsSupportedByAllColumns, filterFunctionsSupportedByTable)); } // Sweep through all column filter capabilities. foreach (var column in columnCapabilitiesJsonObj.EnumerateObject()) { var columnPath = DPath.Root.Append(new DName(column.Name)); // Internal columns don't appear in schema and we don't gather any information about it as they don't appear in expressions. // Task 790576: Runtime should provide visibility information along with delegation metadata information per column if (!schema.Contains(columnPath)) { continue; } // Get capabilities object for column var capabilitiesDefinedByColumn = column.Value; // Get properties object for the column if (capabilitiesDefinedByColumn.TryGetProperty(CapabilitiesConstants.Properties, out var propertyCapabilities)) { foreach (var property in propertyCapabilities.EnumerateObject()) { var propertyPath = columnPath.Append(new DName(property.Name)); var capabilitiesDefinedByColumnProperty = property.Value; if (!capabilitiesDefinedByColumnProperty.TryGetProperty(CapabilitiesConstants.Capabilities, out var propertyCapabilityJsonObject)) { continue; } var propertyCapability = ParseColumnCapability(propertyCapabilityJsonObject, capabilityKey: CapabilitiesConstants.Filter_Functions); if (propertyCapability.Capabilities != DelegationCapability.None) { Contracts.Assert(schema.Contains(propertyPath)); // If column is specified as non-filterable then this metadata shouldn't be present. // But if it is present then we should ignore it. if (!columnRestrictions.ContainsKey(propertyPath)) { columnCapabilities.Add(propertyPath, propertyCapability | DelegationCapability.Filter); } } } } // Get capability object defined for column. // This is optional as for columns with complex types (nested table or record), it will have "properties" key instead. // We are not supporting that case for now. So we ignore it currently. if (!capabilitiesDefinedByColumn.TryGetProperty(CapabilitiesConstants.Capabilities, out var capabilityJsonObject)) { continue; } var isChoice = capabilityJsonObject.TryGetProperty(CapabilitiesConstants.PropertyIsChoice, out var isChoiceElement) && isChoiceElement.GetBoolean(); var capability = ParseColumnCapability(capabilityJsonObject, capabilityKey: CapabilitiesConstants.Filter_Functions); if (capability.Capabilities != DelegationCapability.None) { Contracts.Assert(schema.Contains(columnPath)); // If column is specified as non-filterable then this metadata shouldn't be present. // But if it is present then we should ignore it. if (!columnRestrictions.ContainsKey(columnPath)) { columnCapabilities.Add(columnPath, capability | DelegationCapability.Filter); } if (isChoice == true) { var choicePropertyPath = columnPath.Append(new DName(ODataMetaParser.ValueProperty)); if (!columnRestrictions.ContainsKey(choicePropertyPath)) { columnCapabilities.Add(choicePropertyPath, capability | DelegationCapability.Filter); } } } } return(new FilterOpMetadata(schema, columnRestrictions, columnCapabilities, filterFunctionsSupportedByAllColumns, filterFunctionsSupportedByTable)); }
// Verifies if provided column node supports delegation. protected bool IsDelegatableColumnNode(FirstNameNode node, TexlBinding binding, IOpDelegationStrategy opDelStrategy, DelegationCapability capability) { Contracts.AssertValue(node); Contracts.AssertValue(binding); Contracts.AssertValueOrNull(opDelStrategy); Contracts.Assert(binding.IsRowScope(node)); FirstNameInfo firstNameInfo = binding.GetInfo(node.AsFirstName()); if (firstNameInfo == null) { return(false); } IDelegationMetadata metadata = GetCapabilityMetadata(firstNameInfo); // This means that this row scoped field is from some parent scope which is non-delegatable. That should deny delegation at that point. // For this scope, this means that value will be provided from some other source. // For example, AddColumns(CDS, "Column1", LookUp(CDS1, Name in FirstName)) // CDS - *[Name:s], CDS1 - *[FirstName:s] if (metadata == null) { return(true); } var columnName = firstNameInfo.Name; Contracts.AssertValid(columnName); var columnPath = DPath.Root.Append(columnName); if (!metadata.FilterDelegationMetadata.IsDelegationSupportedByColumn(columnPath, capability)) { var safeColumnName = CharacterUtils.MakeSafeForFormatString(columnName.Value); var message = string.Format(StringResources.Get(TexlStrings.OpNotSupportedByColumnSuggestionMessage_OpNotSupportedByColumn), safeColumnName); SuggestDelegationHintAndAddTelemetryMessage(node, binding, message, TexlStrings.OpNotSupportedByColumnSuggestionMessage_OpNotSupportedByColumn, safeColumnName); TrackingProvider.Instance.SetDelegationTrackerStatus(DelegationStatus.NoDelSupportByColumn, node, binding, _function, DelegationTelemetryInfo.CreateNoDelSupportByColumnTelemetryInfo(firstNameInfo)); return(false); } // If there is any operator applied on this node then check if column supports operation. if (opDelStrategy != null && !opDelStrategy.IsOpSupportedByColumn(metadata.FilterDelegationMetadata, node.AsFirstName(), columnPath, binding)) { return(false); } return(true); }