1 //--------------------------------------------------------------------- 2 // <copyright file="TypeResolver.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // 6 // @owner Microsoft 7 // @backupOwner Microsoft 8 //--------------------------------------------------------------------- 9 10 namespace System.Data.Common.EntitySql 11 { 12 using System; 13 using System.Collections.Generic; 14 using System.Data.Entity; 15 using System.Data.Metadata.Edm; 16 using System.Diagnostics; 17 using System.Linq; 18 19 /// <summary> 20 /// Represents eSQL metadata member expression class. 21 /// </summary> 22 internal enum MetadataMemberClass 23 { 24 Type, 25 FunctionGroup, 26 InlineFunctionGroup, 27 Namespace, 28 EnumMember 29 } 30 31 /// <summary> 32 /// Abstract class representing an eSQL expression classified as <see cref="ExpressionResolutionClass.MetadataMember"/>. 33 /// </summary> 34 internal abstract class MetadataMember : ExpressionResolution 35 { 36 protected MetadataMember(MetadataMemberClass @class, string name) base(ExpressionResolutionClass.MetadataMember)37 : base(ExpressionResolutionClass.MetadataMember) 38 { 39 Debug.Assert(!String.IsNullOrEmpty(name), "name must not be empty"); 40 41 MetadataMemberClass = @class; 42 Name = name; 43 } 44 45 internal override string ExpressionClassName { get { return MetadataMemberExpressionClassName; } } 46 internal static string MetadataMemberExpressionClassName { get { return Strings.LocalizedMetadataMemberExpression; } } 47 48 internal readonly MetadataMemberClass MetadataMemberClass; 49 internal readonly string Name; 50 /// <summary> 51 /// Return the name of the <see cref="MetadataMemberClass"/> for error messages. 52 /// </summary> 53 internal abstract string MetadataMemberClassName { get; } 54 CreateMetadataMemberNameEqualityComparer(StringComparer stringComparer)55 internal static IEqualityComparer<MetadataMember> CreateMetadataMemberNameEqualityComparer(StringComparer stringComparer) 56 { 57 return new MetadataMemberNameEqualityComparer(stringComparer); 58 } 59 60 private sealed class MetadataMemberNameEqualityComparer : IEqualityComparer<MetadataMember> 61 { 62 private readonly StringComparer _stringComparer; 63 MetadataMemberNameEqualityComparer(StringComparer stringComparer)64 internal MetadataMemberNameEqualityComparer(StringComparer stringComparer) 65 { 66 _stringComparer = stringComparer; 67 } 68 Equals(MetadataMember x, MetadataMember y)69 bool IEqualityComparer<MetadataMember>.Equals(MetadataMember x, MetadataMember y) 70 { 71 Debug.Assert(x != null && y != null, "metadata members must not be null"); 72 return _stringComparer.Equals(x.Name, y.Name); 73 } 74 GetHashCode(MetadataMember obj)75 int IEqualityComparer<MetadataMember>.GetHashCode(MetadataMember obj) 76 { 77 Debug.Assert(obj != null, "metadata member must not be null"); 78 return _stringComparer.GetHashCode(obj.Name); 79 } 80 } 81 } 82 83 /// <summary> 84 /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.Namespace"/>. 85 /// </summary> 86 internal sealed class MetadataNamespace : MetadataMember 87 { MetadataNamespace(string name)88 internal MetadataNamespace(string name) : base(MetadataMemberClass.Namespace, name) { } 89 90 internal override string MetadataMemberClassName { get { return NamespaceClassName; } } 91 internal static string NamespaceClassName { get { return Strings.LocalizedNamespace; } } 92 } 93 94 /// <summary> 95 /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.Type"/>. 96 /// </summary> 97 internal sealed class MetadataType : MetadataMember 98 { MetadataType(string name, TypeUsage typeUsage)99 internal MetadataType(string name, TypeUsage typeUsage) 100 : base(MetadataMemberClass.Type, name) 101 { 102 Debug.Assert(typeUsage != null, "typeUsage must not be null"); 103 TypeUsage = typeUsage; 104 } 105 106 internal override string MetadataMemberClassName { get { return TypeClassName; } } 107 internal static string TypeClassName { get { return Strings.LocalizedType; } } 108 109 internal readonly TypeUsage TypeUsage; 110 } 111 112 /// <summary> 113 /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.EnumMember"/>. 114 /// </summary> 115 internal sealed class MetadataEnumMember : MetadataMember 116 { MetadataEnumMember(string name, TypeUsage enumType, EnumMember enumMember)117 internal MetadataEnumMember(string name, TypeUsage enumType, EnumMember enumMember) 118 : base(MetadataMemberClass.EnumMember, name) 119 { 120 Debug.Assert(enumType != null, "enumType must not be null"); 121 Debug.Assert(enumMember != null, "enumMember must not be null"); 122 EnumType = enumType; 123 EnumMember = enumMember; 124 } 125 126 internal override string MetadataMemberClassName { get { return EnumMemberClassName; } } 127 internal static string EnumMemberClassName { get { return Strings.LocalizedEnumMember; } } 128 129 internal readonly TypeUsage EnumType; 130 internal readonly EnumMember EnumMember; 131 } 132 133 /// <summary> 134 /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.FunctionGroup"/>. 135 /// </summary> 136 internal sealed class MetadataFunctionGroup : MetadataMember 137 { MetadataFunctionGroup(string name, IList<EdmFunction> functionMetadata)138 internal MetadataFunctionGroup(string name, IList<EdmFunction> functionMetadata) 139 : base(MetadataMemberClass.FunctionGroup, name) 140 { 141 Debug.Assert(functionMetadata != null && functionMetadata.Count > 0, "FunctionMetadata must not be null or empty"); 142 FunctionMetadata = functionMetadata; 143 } 144 145 internal override string MetadataMemberClassName { get { return FunctionGroupClassName; } } 146 internal static string FunctionGroupClassName { get { return Strings.LocalizedFunction; } } 147 148 internal readonly IList<EdmFunction> FunctionMetadata; 149 } 150 151 /// <summary> 152 /// Represents an eSQL metadata member expression classified as <see cref="MetadataMemberClass.InlineFunctionGroup"/>. 153 /// </summary> 154 internal sealed class InlineFunctionGroup : MetadataMember 155 { InlineFunctionGroup(string name, IList<InlineFunctionInfo> functionMetadata)156 internal InlineFunctionGroup(string name, IList<InlineFunctionInfo> functionMetadata) 157 : base(MetadataMemberClass.InlineFunctionGroup, name) 158 { 159 Debug.Assert(functionMetadata != null && functionMetadata.Count > 0, "FunctionMetadata must not be null or empty"); 160 FunctionMetadata = functionMetadata; 161 } 162 163 internal override string MetadataMemberClassName { get { return InlineFunctionGroupClassName; } } 164 internal static string InlineFunctionGroupClassName { get { return Strings.LocalizedInlineFunction; } } 165 166 internal readonly IList<InlineFunctionInfo> FunctionMetadata; 167 } 168 169 /// <summary> 170 /// Represents eSQL type and namespace name resolver. 171 /// </summary> 172 internal sealed class TypeResolver 173 { 174 private readonly Perspective _perspective; 175 private readonly ParserOptions _parserOptions; 176 private readonly Dictionary<string, MetadataNamespace> _aliasedNamespaces; 177 private readonly HashSet<MetadataNamespace> _namespaces; 178 /// <summary> 179 /// name -> list(overload) 180 /// </summary> 181 private readonly Dictionary<string, List<InlineFunctionInfo>> _functionDefinitions; 182 private bool _includeInlineFunctions; 183 private bool _resolveLeftMostUnqualifiedNameAsNamespaceOnly; 184 185 /// <summary> 186 /// Initializes TypeResolver instance 187 /// </summary> TypeResolver(Perspective perspective, ParserOptions parserOptions)188 internal TypeResolver(Perspective perspective, ParserOptions parserOptions) 189 { 190 EntityUtil.CheckArgumentNull(perspective, "perspective"); 191 192 _perspective = perspective; 193 _parserOptions = parserOptions; 194 _aliasedNamespaces = new Dictionary<string, MetadataNamespace>(parserOptions.NameComparer); 195 _namespaces = new HashSet<MetadataNamespace>(MetadataMember.CreateMetadataMemberNameEqualityComparer(parserOptions.NameComparer)); 196 _functionDefinitions = new Dictionary<string, List<InlineFunctionInfo>>(parserOptions.NameComparer); 197 _includeInlineFunctions = true; 198 _resolveLeftMostUnqualifiedNameAsNamespaceOnly = false; 199 } 200 201 /// <summary> 202 /// Returns perspective. 203 /// </summary> 204 internal Perspective Perspective 205 { 206 get { return _perspective; } 207 } 208 209 /// <summary> 210 /// Returns namespace imports. 211 /// </summary> 212 internal ICollection<MetadataNamespace> NamespaceImports 213 { 214 get { return _namespaces; } 215 } 216 217 /// <summary> 218 /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.String"/>. 219 /// </summary> 220 internal TypeUsage StringType 221 { 222 get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.String); } 223 } 224 225 /// <summary> 226 /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.Boolean"/>. 227 /// </summary> 228 internal TypeUsage BooleanType 229 { 230 get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.Boolean); } 231 } 232 233 /// <summary> 234 /// Returns <see cref="TypeUsage"/> for <see cref="PrimitiveTypeKind.Int64"/>. 235 /// </summary> 236 internal TypeUsage Int64Type 237 { 238 get { return _perspective.MetadataWorkspace.GetCanonicalModelTypeUsage(PrimitiveTypeKind.Int64); } 239 } 240 241 /// <summary> 242 /// Adds an aliased namespace import. 243 /// </summary> AddAliasedNamespaceImport(string alias, MetadataNamespace @namespace, ErrorContext errCtx)244 internal void AddAliasedNamespaceImport(string alias, MetadataNamespace @namespace, ErrorContext errCtx) 245 { 246 if (_aliasedNamespaces.ContainsKey(alias)) 247 { 248 throw EntityUtil.EntitySqlError(errCtx, Strings.NamespaceAliasAlreadyUsed(alias)); 249 } 250 251 _aliasedNamespaces.Add(alias, @namespace); 252 } 253 254 /// <summary> 255 /// Adds a non-aliased namespace import. 256 /// </summary> 257 internal void AddNamespaceImport(MetadataNamespace @namespace, ErrorContext errCtx) 258 { 259 if (_namespaces.Contains(@namespace)) 260 { 261 throw EntityUtil.EntitySqlError(errCtx, Strings.NamespaceAlreadyImported(@namespace.Name)); 262 } 263 264 _namespaces.Add(@namespace); 265 } 266 267 #region Inline function declarations 268 /// <summary> 269 /// Declares inline function in the query local metadata. 270 /// </summary> DeclareInlineFunction(string name, InlineFunctionInfo functionInfo)271 internal void DeclareInlineFunction(string name, InlineFunctionInfo functionInfo) 272 { 273 Debug.Assert(!String.IsNullOrEmpty(name), "name must not be null or empty"); 274 Debug.Assert(functionInfo != null, "functionInfo != null"); 275 276 List<InlineFunctionInfo> overloads; 277 if (!_functionDefinitions.TryGetValue(name, out overloads)) 278 { 279 overloads = new List<InlineFunctionInfo>(); 280 _functionDefinitions.Add(name, overloads); 281 } 282 283 // 284 // Check overload uniqueness. 285 // 286 if (overloads.Exists(overload => 287 overload.Parameters.Select(p => p.ResultType).SequenceEqual(functionInfo.Parameters.Select(p => p.ResultType), TypeUsageStructuralComparer.Instance))) 288 { 289 throw EntityUtil.EntitySqlError(functionInfo.FunctionDefAst.ErrCtx, Strings.DuplicatedInlineFunctionOverload(name)); 290 } 291 292 overloads.Add(functionInfo); 293 } 294 295 private sealed class TypeUsageStructuralComparer : IEqualityComparer<TypeUsage> 296 { 297 internal static readonly TypeUsageStructuralComparer Instance = new TypeUsageStructuralComparer(); 298 TypeUsageStructuralComparer()299 private TypeUsageStructuralComparer() { } 300 Equals(TypeUsage x, TypeUsage y)301 public bool Equals(TypeUsage x, TypeUsage y) 302 { 303 return TypeSemantics.IsStructurallyEqual(x, y); 304 } 305 GetHashCode(TypeUsage obj)306 public int GetHashCode(TypeUsage obj) 307 { 308 Debug.Fail("Not implemented"); 309 return 0; 310 } 311 } 312 #endregion 313 EnterFunctionNameResolution(bool includeInlineFunctions)314 internal IDisposable EnterFunctionNameResolution(bool includeInlineFunctions) 315 { 316 bool savedIncludeInlineFunctions = _includeInlineFunctions; 317 _includeInlineFunctions = includeInlineFunctions; 318 return new Disposer(delegate { this._includeInlineFunctions = savedIncludeInlineFunctions; }); 319 } 320 EnterBackwardCompatibilityResolution()321 internal IDisposable EnterBackwardCompatibilityResolution() 322 { 323 Debug.Assert(!_resolveLeftMostUnqualifiedNameAsNamespaceOnly, "EnterBackwardCompatibilityResolution() is not reentrant."); 324 _resolveLeftMostUnqualifiedNameAsNamespaceOnly = true; 325 return new Disposer(delegate 326 { 327 Debug.Assert(this._resolveLeftMostUnqualifiedNameAsNamespaceOnly, "_resolveLeftMostUnqualifiedNameAsNamespaceOnly must be true."); 328 this._resolveLeftMostUnqualifiedNameAsNamespaceOnly = false; 329 }); 330 } 331 ResolveMetadataMemberName(string[] name, ErrorContext errCtx)332 internal MetadataMember ResolveMetadataMemberName(string[] name, ErrorContext errCtx) 333 { 334 Debug.Assert(name != null && name.Length > 0, "name must not be empty"); 335 336 MetadataMember metadataMember; 337 if (name.Length == 1) 338 { 339 metadataMember = ResolveUnqualifiedName(name[0], false /* partOfQualifiedName */, errCtx); 340 } 341 else 342 { 343 metadataMember = ResolveFullyQualifiedName(name, name.Length, errCtx); 344 } 345 Debug.Assert(metadataMember != null, "metadata member name resolution must not return null"); 346 347 return metadataMember; 348 } 349 ResolveMetadataMemberAccess(MetadataMember qualifier, string name, ErrorContext errCtx)350 internal MetadataMember ResolveMetadataMemberAccess(MetadataMember qualifier, string name, ErrorContext errCtx) 351 { 352 string fullName = GetFullName(qualifier.Name, name); 353 if (qualifier.MetadataMemberClass == MetadataMemberClass.Namespace) 354 { 355 // 356 // Try resolving as a type. 357 // 358 MetadataType type; 359 if (TryGetTypeFromMetadata(fullName, out type)) 360 { 361 return type; 362 } 363 364 // 365 // Try resolving as a function. 366 // 367 MetadataFunctionGroup function; 368 if (TryGetFunctionFromMetadata(qualifier.Name, name, out function)) 369 { 370 return function; 371 } 372 373 // 374 // Otherwise, resolve as a namespace. 375 // 376 return new MetadataNamespace(fullName); 377 } 378 else if (qualifier.MetadataMemberClass == MetadataMemberClass.Type) 379 { 380 var type = (MetadataType)qualifier; 381 if (TypeSemantics.IsEnumerationType(type.TypeUsage)) 382 { 383 EnumMember member; 384 if (_perspective.TryGetEnumMember((EnumType)type.TypeUsage.EdmType, name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out member)) 385 { 386 Debug.Assert(member != null, "member != null"); 387 Debug.Assert(_parserOptions.NameComparer.Equals(name, member.Name), "_parserOptions.NameComparer.Equals(name, member.Name)"); 388 return new MetadataEnumMember(fullName, type.TypeUsage, member); 389 } 390 else 391 { 392 throw EntityUtil.EntitySqlError(errCtx, Strings.NotAMemberOfType(name, qualifier.Name)); 393 } 394 } 395 } 396 397 throw EntityUtil.EntitySqlError(errCtx, Strings.InvalidMetadataMemberClassResolution( 398 qualifier.Name, qualifier.MetadataMemberClassName, MetadataNamespace.NamespaceClassName)); 399 } 400 ResolveUnqualifiedName(string name, bool partOfQualifiedName, ErrorContext errCtx)401 internal MetadataMember ResolveUnqualifiedName(string name, bool partOfQualifiedName, ErrorContext errCtx) 402 { 403 Debug.Assert(!String.IsNullOrEmpty(name), "name must not be empty"); 404 405 // 406 // In the case of Name1.Name2...NameN and if backward compatibility mode is on, then resolve Name1 as namespace only, ignore any other possible resolutions. 407 // 408 bool resolveAsNamespaceOnly = partOfQualifiedName && _resolveLeftMostUnqualifiedNameAsNamespaceOnly; 409 410 // 411 // In the case of Name1.Name2...NameN, ignore functions while resolving Name1: functions don't have members. 412 // 413 bool includeFunctions = !partOfQualifiedName; 414 415 // 416 // Try resolving as an inline function. 417 // 418 InlineFunctionGroup inlineFunctionGroup; 419 if (!resolveAsNamespaceOnly && 420 includeFunctions && TryGetInlineFunction(name, out inlineFunctionGroup)) 421 { 422 return inlineFunctionGroup; 423 } 424 425 // 426 // Try resolving as a namespace alias. 427 // 428 MetadataNamespace aliasedNamespaceImport; 429 if (_aliasedNamespaces.TryGetValue(name, out aliasedNamespaceImport)) 430 { 431 return aliasedNamespaceImport; 432 } 433 434 if (!resolveAsNamespaceOnly) 435 { 436 // 437 // Try resolving as a type or functionGroup in the global namespace or as an imported member. 438 // Throw if ambiguous. 439 // 440 MetadataType type = null; 441 MetadataFunctionGroup functionGroup = null; 442 443 if (!TryGetTypeFromMetadata(name, out type)) 444 { 445 if (includeFunctions) 446 { 447 // 448 // If name looks like a multipart identifier, try resolving it in the global namespace. 449 // Escaped multipart identifiers usually appear in views: select [NS1.NS2.Product](...) from ... 450 // 451 var multipart = name.Split('.'); 452 if (multipart.Length > 1 && multipart.All(p => p.Length > 0)) 453 { 454 var functionName = multipart[multipart.Length - 1]; 455 var namespaceName = name.Substring(0, name.Length - functionName.Length - 1); 456 TryGetFunctionFromMetadata(namespaceName, functionName, out functionGroup); 457 } 458 } 459 } 460 461 // 462 // Try resolving as an imported member. 463 // 464 MetadataNamespace importedMemberNamespace = null; 465 foreach (MetadataNamespace namespaceImport in _namespaces) 466 { 467 string fullName = GetFullName(namespaceImport.Name, name); 468 469 MetadataType importedType; 470 if (TryGetTypeFromMetadata(fullName, out importedType)) 471 { 472 if (type == null && functionGroup == null) 473 { 474 type = importedType; 475 importedMemberNamespace = namespaceImport; 476 } 477 else 478 { 479 throw AmbiguousMetadataMemberName(errCtx, name, namespaceImport, importedMemberNamespace); 480 } 481 } 482 483 MetadataFunctionGroup importedFunctionGroup; 484 if (includeFunctions && TryGetFunctionFromMetadata(namespaceImport.Name, name, out importedFunctionGroup)) 485 { 486 if (type == null && functionGroup == null) 487 { 488 functionGroup = importedFunctionGroup; 489 importedMemberNamespace = namespaceImport; 490 } 491 else 492 { 493 throw AmbiguousMetadataMemberName(errCtx, name, namespaceImport, importedMemberNamespace); 494 } 495 } 496 } 497 if (type != null) 498 { 499 return type; 500 } 501 if (functionGroup != null) 502 { 503 return functionGroup; 504 } 505 } 506 507 // 508 // Otherwise, resolve as a namespace. 509 // 510 return new MetadataNamespace(name); 511 } 512 ResolveFullyQualifiedName(string[] name, int length, ErrorContext errCtx)513 private MetadataMember ResolveFullyQualifiedName(string[] name, int length, ErrorContext errCtx) 514 { 515 Debug.Assert(name != null && length > 1 && length <= name.Length, "name must not be empty"); 516 517 // 518 // Resolve N in N.R 519 // 520 MetadataMember left; 521 if (length == 2) 522 { 523 // 524 // If N is a single name, ignore functions: functions don't have members. 525 // 526 left = ResolveUnqualifiedName(name[0], true /* partOfQualifiedName */, errCtx); 527 } 528 else 529 { 530 left = ResolveFullyQualifiedName(name, length - 1, errCtx); 531 } 532 533 // 534 // Get R in N.R 535 // 536 string rightName = name[length - 1]; 537 Debug.Assert(!String.IsNullOrEmpty(rightName), "rightName must not be empty"); 538 539 // 540 // Resolve R in the context of N 541 // 542 return ResolveMetadataMemberAccess(left, rightName, errCtx); 543 } 544 AmbiguousMetadataMemberName(ErrorContext errCtx, string name, MetadataNamespace ns1, MetadataNamespace ns2)545 private static Exception AmbiguousMetadataMemberName(ErrorContext errCtx, string name, MetadataNamespace ns1, MetadataNamespace ns2) 546 { 547 throw EntityUtil.EntitySqlError(errCtx, Strings.AmbiguousMetadataMemberName(name, ns1.Name, ns2 != null ? ns2.Name : null)); 548 } 549 550 /// <summary> 551 /// Try get type from the model using the fully qualified name. 552 /// </summary> TryGetTypeFromMetadata(string typeFullName, out MetadataType type)553 private bool TryGetTypeFromMetadata(string typeFullName, out MetadataType type) 554 { 555 TypeUsage typeUsage; 556 if (_perspective.TryGetTypeByName(typeFullName, _parserOptions.NameComparisonCaseInsensitive /* ignore case */, out typeUsage)) 557 { 558 type = new MetadataType(typeFullName, typeUsage); 559 return true; 560 } 561 else 562 { 563 type = null; 564 return false; 565 } 566 } 567 568 /// <summary> 569 /// Try get function from the model using the fully qualified name. 570 /// </summary> TryGetFunctionFromMetadata(string namespaceName, string functionName, out MetadataFunctionGroup functionGroup)571 internal bool TryGetFunctionFromMetadata(string namespaceName, string functionName, out MetadataFunctionGroup functionGroup) 572 { 573 IList<EdmFunction> functionMetadata; 574 if (_perspective.TryGetFunctionByName(namespaceName, functionName, _parserOptions.NameComparisonCaseInsensitive /* ignore case */, out functionMetadata)) 575 { 576 functionGroup = new MetadataFunctionGroup(GetFullName(namespaceName, functionName), functionMetadata); 577 return true; 578 } 579 else 580 { 581 functionGroup = null; 582 return false; 583 } 584 } 585 586 /// <summary> 587 /// Try get function from the local metadata using the fully qualified name. 588 /// </summary> TryGetInlineFunction(string functionName, out InlineFunctionGroup inlineFunctionGroup)589 private bool TryGetInlineFunction(string functionName, out InlineFunctionGroup inlineFunctionGroup) 590 { 591 List<InlineFunctionInfo> inlineFunctionMetadata; 592 if (_includeInlineFunctions && _functionDefinitions.TryGetValue(functionName, out inlineFunctionMetadata)) 593 { 594 inlineFunctionGroup = new InlineFunctionGroup(functionName, inlineFunctionMetadata); 595 return true; 596 } 597 else 598 { 599 inlineFunctionGroup = null; 600 return false; 601 } 602 } 603 604 /// <summary> 605 /// Builds a dot-separated multipart identifier off the provided <paramref name="names"/>. 606 /// </summary> GetFullName(params string[] names)607 internal static string GetFullName(params string[] names) 608 { 609 Debug.Assert(names != null && names.Length > 0, "names must not be null or empty"); 610 return String.Join(".", names); 611 } 612 } 613 } 614