You've already forked linux-packaging-mono
							
							
		
			
				
	
	
		
			554 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			554 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| //---------------------------------------------------------------------
 | |
| // <copyright file="StorageEntityContainerMapping.cs" company="Microsoft">
 | |
| //      Copyright (c) Microsoft Corporation.  All rights reserved.
 | |
| // </copyright>
 | |
| //
 | |
| // @owner       Microsoft
 | |
| // @backupOwner Microsoft
 | |
| //---------------------------------------------------------------------
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Collections.ObjectModel;
 | |
| using System.Linq;
 | |
| using System.Text;
 | |
| using System.Xml;
 | |
| using System.Data.Metadata.Edm;
 | |
| using System.Diagnostics;
 | |
| using System.Data.Mapping.ViewGeneration;
 | |
| using System.Data.Common.Utils;
 | |
| using System.Data.Mapping.ViewGeneration.Structures;
 | |
| using System.Data.Mapping.ViewGeneration.Validation;
 | |
| 
 | |
| namespace System.Data.Mapping {
 | |
| 
 | |
|     using CellGroup = Set<Cell>;
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Represents the Mapping metadata for the EntityContainer map in CS space.
 | |
|     /// Only one EntityContainerMapping element is allowed in the MSL file for CS mapping.
 | |
|     /// <example>
 | |
|     /// For Example if conceptually you could represent the CS MSL file as following
 | |
|     /// ---Mapping 
 | |
|     ///    --EntityContainerMapping ( CNorthwind-->SNorthwind )
 | |
|     ///      --EntitySetMapping
 | |
|     ///      --AssociationSetMapping 
 | |
|     /// The type represents the metadata for EntityContainerMapping element in the above example.
 | |
|     /// The SetMapping elements that are children of the EntityContainerMapping element
 | |
|     /// can be accessed through the properties on this type.
 | |
|     /// </example>
 | |
|     /// <remarks>
 | |
|     /// We currently assume that an Entity Container on the C side
 | |
|     /// is mapped to a single Entity Container in the S - space.
 | |
|     /// </remarks>
 | |
|     /// </summary>
 | |
|     internal class StorageEntityContainerMapping : Map
 | |
|  {
 | |
|         #region Constructors
 | |
|         /// <summary>
 | |
|         /// Construct a new EntityContainer mapping object 
 | |
|         /// passing in the C-space EntityContainer  and
 | |
|         /// the s-space Entity container metadata objects.
 | |
|         /// </summary>
 | |
|         /// <param name="entityContainer">Entity Continer type that is being mapped on the C-side</param>
 | |
|         /// <param name="storageEntityContainer">Entity Continer type that is being mapped on the S-side</param>
 | |
|         internal StorageEntityContainerMapping(EntityContainer entityContainer, EntityContainer storageEntityContainer, StorageMappingItemCollection storageMappingItemCollection, bool validate, bool generateUpdateViews)
 | |
|         {
 | |
|             this.m_entityContainer = entityContainer;
 | |
|             this.m_storageEntityContainer = storageEntityContainer;
 | |
|             this.m_storageMappingItemCollection = storageMappingItemCollection;
 | |
|             this.m_memoizedCellGroupEvaluator = new Memoizer<InputForComputingCellGroups, OutputFromComputeCellGroups>(ComputeCellGroups, new InputForComputingCellGroups());
 | |
|             this.identity = entityContainer.Identity;
 | |
|             this.m_validate = validate;
 | |
|             this.m_generateUpdateViews = generateUpdateViews;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Fields
 | |
|         private string identity;
 | |
|         private bool m_validate;
 | |
|         private bool m_generateUpdateViews;
 | |
|         private EntityContainer m_entityContainer;  //Entity Continer type that is being mapped on the C-side
 | |
|         private EntityContainer m_storageEntityContainer;  //Entity Continer type that the C-space container is being mapped to
 | |
|         private Dictionary<string, StorageSetMapping> m_entitySetMappings = new Dictionary<string, StorageSetMapping>(StringComparer.Ordinal);  //A collection of EntitySetMappings under this EntityContainer mapping
 | |
|         private Dictionary<string, StorageSetMapping> m_associationSetMappings = new Dictionary<string, StorageSetMapping>(StringComparer.Ordinal);  //A collection of AssociationSetMappings under this EntityContainer mapping        
 | |
|         private Dictionary<EdmFunction, FunctionImportMapping> m_functionImportMappings = new Dictionary<EdmFunction, FunctionImportMapping>();
 | |
|         private string m_sourceLocation; //Schema URI for the mapping
 | |
|         private int m_startLineNumber; //Line Number for EntityContainer Mapping element start tag
 | |
|         private int m_startLinePosition; //Line position for EntityContainer Mapping element start tag
 | |
|         private readonly StorageMappingItemCollection m_storageMappingItemCollection;
 | |
|         private readonly Memoizer<InputForComputingCellGroups, OutputFromComputeCellGroups> m_memoizedCellGroupEvaluator;
 | |
| 
 | |
|         #endregion
 | |
| 
 | |
|         #region Properties
 | |
|         public StorageMappingItemCollection StorageMappingItemCollection
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return m_storageMappingItemCollection;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets the type kind for this item
 | |
|         /// </summary>
 | |
|         public override BuiltInTypeKind BuiltInTypeKind {
 | |
|             get { return BuiltInTypeKind.MetadataItem; }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The Entity Container Metadata object on the C-side
 | |
|         /// for which the mapping is being represented.
 | |
|         /// </summary>
 | |
|         internal override MetadataItem EdmItem {
 | |
|             get {
 | |
|                 return this.m_entityContainer;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         internal override string Identity {
 | |
|             get {
 | |
|                 return identity;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Indicates whether there are no Set mappings
 | |
|         /// in the container mapping.
 | |
|         /// </summary>
 | |
|         internal bool IsEmpty
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return ((m_entitySetMappings.Count == 0)
 | |
|                 && (m_associationSetMappings.Count == 0));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Determine whether the container includes any views.
 | |
|         /// Returns true if there is at least one query or update view specified by the mapping.
 | |
|         /// </summary>
 | |
|         internal bool HasViews
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return HasMappingFragments()
 | |
|                     || AllSetMaps.Any((StorageSetMapping setMap) => setMap.QueryView != null);
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
|         internal string SourceLocation {
 | |
|             get { return m_sourceLocation; }
 | |
|             set { m_sourceLocation = value; }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The Entity Container Metadata object on the C-side
 | |
|         /// for which the mapping is being represented.
 | |
|         /// </summary>
 | |
|         internal EntityContainer EdmEntityContainer {
 | |
|             get {
 | |
|                 return this.m_entityContainer;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The Entity Container Metadata object on the C-side
 | |
|         /// for which the mapping is being represented.
 | |
|         /// </summary>
 | |
|         internal EntityContainer StorageEntityContainer {
 | |
|             get {
 | |
|                 return this.m_storageEntityContainer;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// a list of all the  entity set maps under this
 | |
|         /// container. In CS mapping, the mapping is done
 | |
|         /// at the extent level as opposed to the type level.
 | |
|         /// </summary>
 | |
|         internal ReadOnlyCollection<StorageSetMapping> EntitySetMaps {
 | |
|             get {
 | |
|                 return new List<StorageSetMapping>(this.m_entitySetMappings.Values).AsReadOnly();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// a list of all the  entity set maps under this
 | |
|         /// container. In CS mapping, the mapping is done
 | |
|         /// at the extent level as opposed to the type level.
 | |
|         /// RelationshipSetMaps will be CompositionSetMaps and
 | |
|         /// AssociationSetMaps put together.
 | |
|         /// </summary>
 | |
|         /// <remarks>
 | |
|         /// The reason we have RelationshipSetMaps is to be consistent with CDM metadata
 | |
|         /// which treats both associations and compositions as Relationships.
 | |
|         /// </remarks>
 | |
|         internal ReadOnlyCollection<StorageSetMapping> RelationshipSetMaps {
 | |
|             get {
 | |
|                 return new List<StorageSetMapping>(this.m_associationSetMappings.Values).AsReadOnly();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// a list of all the  set maps under this
 | |
|         /// container. 
 | |
|         /// </summary>
 | |
|         internal IEnumerable<StorageSetMapping> AllSetMaps
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return System.Linq.Enumerable.Concat(this.m_entitySetMappings.Values, this.m_associationSetMappings.Values);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Line Number in MSL file where the EntityContainer Mapping Element's Start Tag is present.
 | |
|         /// </summary>
 | |
|         internal int StartLineNumber
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return m_startLineNumber;
 | |
|             }
 | |
|             set
 | |
|             {
 | |
|                 m_startLineNumber = value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Line Position in MSL file where the EntityContainer Mapping Element's Start Tag is present.
 | |
|         /// </summary>
 | |
|         internal int StartLinePosition
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return m_startLinePosition;
 | |
|             }
 | |
|             set
 | |
|             {
 | |
|                 m_startLinePosition = value;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Indicates whether to validate the mapping or not.
 | |
|         /// </summary>
 | |
|         internal bool Validate
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return m_validate;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Indicates whether to generate the update views or not.
 | |
|         /// </summary>
 | |
|         internal bool GenerateUpdateViews
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return m_generateUpdateViews;
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Methods
 | |
|         /// <summary>
 | |
|         /// get an EntitySet mapping based upon the name of the entity set.
 | |
|         /// </summary>
 | |
|         /// /// <param name="entitySetName">the name of the entity set</param>
 | |
|         internal StorageSetMapping GetEntitySetMapping(String entitySetName) {
 | |
|             EntityUtil.CheckArgumentNull(entitySetName, "entitySetName");
 | |
|             //Key for EntitySetMapping should be EntitySet name and Entoty type name
 | |
|             StorageSetMapping setMapping = null;
 | |
|             m_entitySetMappings.TryGetValue(entitySetName, out setMapping);
 | |
|             return setMapping;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Get a RelationShip set mapping based upon the name of the relationship set
 | |
|         /// </summary>
 | |
|         /// <param name="relationshipSetName">the name of the relationship set</param>
 | |
|         /// <returns>the mapping for the entity set if it exists, null if it does not exist</returns>
 | |
|         internal StorageSetMapping GetRelationshipSetMapping(string relationshipSetName) {
 | |
|             EntityUtil.CheckArgumentNull(relationshipSetName, "relationshipSetName");
 | |
|             StorageSetMapping setMapping = null;
 | |
|             m_associationSetMappings.TryGetValue(relationshipSetName, out setMapping);
 | |
|             return setMapping;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Get a RelationShipSet mapping that has the passed in EntitySet as one of the ends and is mapped to the
 | |
|         /// table.
 | |
|         /// </summary>
 | |
|         internal IEnumerable<StorageAssociationSetMapping> GetRelationshipSetMappingsFor(EntitySetBase edmEntitySet, EntitySetBase storeEntitySet )
 | |
|         {
 | |
|             //First select the association set maps that are mapped to this table
 | |
|             IEnumerable<StorageAssociationSetMapping> associationSetMappings = m_associationSetMappings.Values.Cast<StorageAssociationSetMapping>().Where(w => ((w.StoreEntitySet != null) && (w.StoreEntitySet == storeEntitySet)));
 | |
|             //From this again filter the ones that have the specified EntitySet on atleast one end
 | |
|             associationSetMappings = associationSetMappings.Where(associationSetMap => ((associationSetMap.Set as AssociationSet).AssociationSetEnds.Any(associationSetEnd => associationSetEnd.EntitySet == edmEntitySet)));
 | |
|             return associationSetMappings;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Get a set mapping based upon the name of the set
 | |
|         /// </summary>
 | |
|         /// <param name="setName"></param>
 | |
|         /// <returns></returns>
 | |
|         internal StorageSetMapping GetSetMapping(string setName)
 | |
|         {
 | |
|             StorageSetMapping setMap = GetEntitySetMapping(setName);
 | |
|             if (setMap == null)
 | |
|             {
 | |
|                 setMap = GetRelationshipSetMapping(setName);
 | |
|             }
 | |
|             return setMap;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds an entity set mapping to the list of EntitySetMaps
 | |
|         /// under this entity container mapping. The method will be called
 | |
|         /// by the Mapping loader.
 | |
|         /// </summary>
 | |
|         internal void AddEntitySetMapping(StorageSetMapping setMapping) {
 | |
|             if (!this.m_entitySetMappings.ContainsKey(setMapping.Set.Name))
 | |
|                 this.m_entitySetMappings.Add(setMapping.Set.Name, setMapping);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds a association set mapping to the list of AssociationSetMaps
 | |
|         /// under this entity container mapping. The method will be called
 | |
|         /// by the Mapping loader.
 | |
|         /// </summary>
 | |
|         internal void AddAssociationSetMapping(StorageSetMapping setMapping) {
 | |
|             this.m_associationSetMappings.Add(setMapping.Set.Name, setMapping);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// check whether the EntityContainerMapping contains
 | |
|         /// the map for the given AssociationSet
 | |
|         /// </summary>
 | |
|         /// <param name="associationSet"></param>
 | |
|         /// <returns></returns>
 | |
|         internal bool ContainsAssociationSetMapping(AssociationSet associationSet) {
 | |
|             return this.m_associationSetMappings.ContainsKey(associationSet.Name);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns whether the Set Map for the given set has a query view or not
 | |
|         /// </summary>
 | |
|         /// <param name="setName"></param>
 | |
|         /// <returns></returns>
 | |
|         internal bool HasQueryViewForSetMap(string setName)
 | |
|         {            
 | |
|             StorageSetMapping set = GetSetMapping(setName);
 | |
|             if (set != null)
 | |
|             {
 | |
|                 return (set.QueryView != null);
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         internal bool HasMappingFragments()
 | |
|         {
 | |
|             foreach (var extentMap in this.AllSetMaps)
 | |
|             {
 | |
|                 foreach (var typeMap in extentMap.TypeMappings)
 | |
|                 {
 | |
|                     if (typeMap.MappingFragments.Count > 0)
 | |
|                     {
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         ///<summary>
 | |
|         /// The method builds up the spaces required for pretty printing each 
 | |
|         /// part of the mapping.
 | |
|         ///</summary>
 | |
|         internal static string GetPrettyPrintString(ref int index) {
 | |
| 
 | |
|             string spaces = "";
 | |
|             spaces = spaces.PadLeft(index, ' ');
 | |
|             Console.WriteLine(spaces + "|");
 | |
|             Console.WriteLine(spaces + "|");
 | |
|             index++;
 | |
|             spaces = spaces.PadLeft(index, ' ');
 | |
|             Console.Write(spaces + "-");
 | |
|             index++;
 | |
|             spaces = spaces.PadLeft(index, ' ');
 | |
|             Console.Write("-");
 | |
|             index++;
 | |
|             spaces = spaces.PadLeft(index, ' ');
 | |
|             return spaces;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// This method is primarily for debugging purposes.
 | |
|         /// Will be removed shortly.
 | |
|         /// </summary>
 | |
|         /// <param name="index"></param>
 | |
|         internal void Print(int index) {
 | |
|             string spaces = "";
 | |
|             StringBuilder sb = new StringBuilder();
 | |
|             sb.Append(spaces);
 | |
|             sb.Append("EntityContainerMapping");
 | |
|             sb.Append("   ");
 | |
|             sb.Append("Name:");
 | |
|             sb.Append(this.m_entityContainer.Name);
 | |
|             sb.Append("   ");
 | |
|             Console.WriteLine(sb.ToString());
 | |
|             foreach (StorageSetMapping extentMapping in m_entitySetMappings.Values) {
 | |
|                 extentMapping.Print(index + 5);
 | |
|             }
 | |
|             foreach (StorageSetMapping extentMapping in m_associationSetMappings.Values) {
 | |
|                 extentMapping.Print(index + 5);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Methods to modify and access function imports, which association a "functionImport" declared
 | |
|         // in the model entity container with a targetFunction declared in the target
 | |
|         internal void AddFunctionImportMapping(EdmFunction functionImport, FunctionImportMapping mapping)
 | |
|         {
 | |
|             m_functionImportMappings.Add(functionImport, mapping);
 | |
|         }
 | |
| 
 | |
|         internal bool TryGetFunctionImportMapping(EdmFunction functionImport, out FunctionImportMapping mapping)
 | |
|         {
 | |
|             return m_functionImportMappings.TryGetValue(functionImport, out mapping);
 | |
|         }
 | |
| 
 | |
|         internal OutputFromComputeCellGroups GetCellgroups(InputForComputingCellGroups args)
 | |
|         { 
 | |
|             Debug.Assert(object.ReferenceEquals(this, args.ContainerMapping));
 | |
|             return m_memoizedCellGroupEvaluator.Evaluate(args);
 | |
|         }
 | |
| 
 | |
|         private OutputFromComputeCellGroups ComputeCellGroups(InputForComputingCellGroups args)
 | |
|         {
 | |
|             OutputFromComputeCellGroups result = new OutputFromComputeCellGroups();
 | |
|             result.Success = true;
 | |
| 
 | |
|             CellCreator cellCreator = new CellCreator(args.ContainerMapping);
 | |
|             result.Cells = cellCreator.GenerateCells(args.Config);
 | |
|             result.Identifiers = cellCreator.Identifiers;
 | |
| 
 | |
|             if (result.Cells.Count <= 0)
 | |
|             {
 | |
|                 //When type-specific QVs are asked for but not defined in the MSL we should return without generating
 | |
|                 // Query pipeline will handle this appropriately by asking for UNION ALL view.
 | |
|                 result.Success = false;
 | |
|                 return result;
 | |
|             }
 | |
| 
 | |
|             result.ForeignKeyConstraints = ForeignConstraint.GetForeignConstraints(args.ContainerMapping.StorageEntityContainer);
 | |
| 
 | |
|             // Go through each table and determine their foreign key constraints
 | |
|             CellPartitioner partitioner = new CellPartitioner(result.Cells, result.ForeignKeyConstraints);
 | |
|             List<CellGroup> cellGroups = partitioner.GroupRelatedCells();
 | |
| 
 | |
|             //Clone cell groups- i.e, List<Set<Cell>> - upto cell before storing it in the cache because viewgen modified the Cell structure
 | |
|             result.CellGroups = cellGroups.Select(setOfcells => new CellGroup(setOfcells.Select(cell => new Cell(cell)))).ToList();
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
| 
 | |
|         #endregion
 | |
|     }
 | |
| 
 | |
|     internal struct InputForComputingCellGroups : IEquatable<InputForComputingCellGroups>, IEqualityComparer<InputForComputingCellGroups>
 | |
|     {
 | |
|         internal readonly StorageEntityContainerMapping ContainerMapping;
 | |
|         internal readonly ConfigViewGenerator Config;
 | |
| 
 | |
|         internal InputForComputingCellGroups(StorageEntityContainerMapping containerMapping, ConfigViewGenerator config)
 | |
|         {
 | |
|             this.ContainerMapping = containerMapping;
 | |
|             this.Config = config;
 | |
|         }
 | |
| 
 | |
|         public bool Equals(InputForComputingCellGroups other)
 | |
|         {
 | |
|             // Isn't this funny? We are not using Memoizer for function memoization. Args Entity and Config don't matter!
 | |
|             // If I were to compare Entity this would not use the cache for cases when I supply different entity set. However,
 | |
|             // the cell groups belong to ALL entity sets.
 | |
|             return (this.ContainerMapping.Equals(other.ContainerMapping) 
 | |
|                 && this.Config.Equals(other.Config));
 | |
|         }
 | |
| 
 | |
|         public bool Equals(InputForComputingCellGroups one, InputForComputingCellGroups two)
 | |
|         {
 | |
|             if (object.ReferenceEquals(one, two))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|             if (object.ReferenceEquals(one, null) || object.ReferenceEquals(two, null))
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             return one.Equals(two);
 | |
|         }
 | |
| 
 | |
|         public int GetHashCode(InputForComputingCellGroups value)
 | |
|         {
 | |
|             if (value == null)
 | |
|             {
 | |
|                 return 0;
 | |
|             }
 | |
| 
 | |
|             return value.GetHashCode();
 | |
|         }
 | |
| 
 | |
|         public override int GetHashCode()
 | |
|         {
 | |
|             return this.ContainerMapping.GetHashCode();
 | |
|         }
 | |
| 
 | |
|         public override bool Equals(object obj)
 | |
|         {
 | |
|             if (obj is InputForComputingCellGroups)
 | |
|             {
 | |
|                 return Equals((InputForComputingCellGroups)obj);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public static bool operator ==(InputForComputingCellGroups input1, InputForComputingCellGroups input2)
 | |
|         {
 | |
|             if (object.ReferenceEquals(input1, input2))
 | |
|             {
 | |
|                 return true;
 | |
|             }
 | |
|             return input1.Equals(input2);
 | |
|         }
 | |
| 
 | |
|         public static bool operator !=(InputForComputingCellGroups input1, InputForComputingCellGroups input2)
 | |
|         {
 | |
|             return !(input1 == input2);
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     internal struct OutputFromComputeCellGroups
 | |
|     {
 | |
|         internal List<Cell> Cells;
 | |
|         internal CqlIdentifiers Identifiers;
 | |
|         internal List<CellGroup> CellGroups;
 | |
|         internal List<ForeignConstraint> ForeignKeyConstraints;
 | |
|         internal bool Success;
 | |
|     }
 | |
| }
 |