/// <summary>
        /// Constructs the graph pattern specified by the MATCH clause. 
        /// The graph pattern may consist of multiple fully-connected sub-graphs.
        /// </summary>
        /// <param name="query">The SELECT query block</param>
        /// <returns>A graph object contains all the connected componeents</returns>
        private MatchGraph ConstructGraph(WSelectQueryBlock query)
        {
            if (query == null || query.MatchClause == null)
                return null;

            var columnsOfNodeTables = _graphMetaData.ColumnsOfNodeTables;
            var nodeViewMapping = _graphMetaData.NodeViewMapping;
            var unionFind = new UnionFind();
            var edgeColumnToAliasesDict = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
            var pathDictionary = new Dictionary<string, MatchPath>(StringComparer.OrdinalIgnoreCase);
            var reversedEdgeDict = new Dictionary<string, MatchEdge>();
            var matchClause = query.MatchClause;
            var nodes = new Dictionary<string, MatchNode>(StringComparer.OrdinalIgnoreCase);
            var connectedSubGraphs = new List<ConnectedComponent>();
            var subGrpahMap = new Dictionary<string, ConnectedComponent>(StringComparer.OrdinalIgnoreCase);
            var parent = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            unionFind.Parent = parent;

            // Constructs the graph pattern specified by the path expressions in the MATCH clause
            foreach (var path in matchClause.Paths)
            {
                var index = 0;
                MatchEdge preEdge = null;
                for (var count = path.PathEdgeList.Count; index < count; ++index)
                {
                    var currentNodeTableRef = path.PathEdgeList[index].Item1;
                    var currentEdgeColumnRef = path.PathEdgeList[index].Item2;
                    var currentNodeExposedName = currentNodeTableRef.BaseIdentifier.Value;
                    var nextNodeTableRef = index != count - 1
                        ? path.PathEdgeList[index + 1].Item1
                        : path.Tail;
                    var nextNodeExposedName = nextNodeTableRef.BaseIdentifier.Value;
                    var patternNode = nodes.GetOrCreate(currentNodeExposedName);
                    if (patternNode.NodeAlias == null)
                    {
                        patternNode.NodeAlias = currentNodeExposedName;
                        patternNode.Neighbors = new List<MatchEdge>();
                        patternNode.External = false;
                        var nodeTable = _context[currentNodeExposedName] as WNamedTableReference;
                        if (nodeTable != null)
                        {
                            patternNode.NodeTableObjectName = nodeTable.TableObjectName;
                            if (patternNode.NodeTableObjectName.SchemaIdentifier == null)
                                patternNode.NodeTableObjectName.Identifiers.Insert(0, new Identifier {Value = "dbo"});
                        }
                    }

                    Identifier edgeIdentifier = currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last();
                    string schema = patternNode.NodeTableObjectName.SchemaIdentifier.Value.ToLower();
                    string nodeTableName = patternNode.NodeTableObjectName.BaseIdentifier.Value;
                    string bindTableName =
                        _context.EdgeNodeBinding[
                            new Tuple<string, string>(nodeTableName.ToLower(), edgeIdentifier.Value.ToLower())].ToLower();
                    var bindNodeTableObjName = new WSchemaObjectName(
                        new Identifier {Value = schema},
                        new Identifier {Value = bindTableName}
                        );
                    var edgeColumn =
                        columnsOfNodeTables[
                            WNamedTableReference.SchemaNameToTuple(bindNodeTableObjName)][
                                currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last().Value];
                    string edgeAlias = currentEdgeColumnRef.Alias;
                    //string revEdgeAlias = edgeAlias;
                    bool isReversed = path.IsReversed || edgeColumn.EdgeInfo.IsReversedEdge;
                    string currentRevEdgeName = null;

                    // get original edge name
                    var currentEdgeName = currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last().Value;
                    var originalSourceName = isReversed ?
                        (_context[nextNodeExposedName] as WNamedTableReference).TableObjectName.BaseIdentifier.Value
                        : (_context[currentNodeExposedName] as WNamedTableReference).TableObjectName.BaseIdentifier.Value;

                    if (isReversed)
                    {
                        var i = currentEdgeName.IndexOf(originalSourceName, StringComparison.OrdinalIgnoreCase) +
                            originalSourceName.Length;
                        currentRevEdgeName = currentEdgeName.Substring(i + 1,
                            currentEdgeName.Length - "Reversed".Length - i - 1);
                    }
                    else
                    {
                        var srcTuple = WNamedTableReference.SchemaNameToTuple(patternNode.NodeTableObjectName);
                        // [nodeView]-[edge]->[node/nodeView]
                        if (edgeColumn.Role == WNodeTableColumnRole.Edge && nodeViewMapping.ContainsKey(srcTuple))
                        {
                            var physicalNodeName =
                                _context.EdgeNodeBinding[new Tuple<string, string>(srcTuple.Item2, currentEdgeName.ToLower())];
                            currentRevEdgeName = physicalNodeName + "_" + currentEdgeName + "Reversed";
                        }
                        else
                        {
                            currentRevEdgeName = originalSourceName + "_" + currentEdgeName + "Reversed";
                        }
                    }

                    if (edgeAlias == null)
                    {
                        edgeAlias = !isReversed
                            ? string.Format("{0}_{1}_{2}", currentNodeExposedName, currentEdgeName, nextNodeExposedName)
                            : string.Format("{0}_{1}_{2}", nextNodeExposedName, currentRevEdgeName, currentNodeExposedName);

                        //when the current edge is a reversed edge, the key should still be the original edge name
                        //e.g.: TestDeleteEdgeWithTableAlias
                        var edgeNameKey = isReversed ? currentRevEdgeName : currentEdgeName;
                        if (edgeColumnToAliasesDict.ContainsKey(edgeNameKey))
                        {
                            edgeColumnToAliasesDict[edgeNameKey].Add(edgeAlias);
                        }
                        else
                        {
                            edgeColumnToAliasesDict.Add(edgeNameKey, new List<string> { edgeAlias });
                        }
                    }

                    MatchEdge edge, revEdge;
                    if (currentEdgeColumnRef.MinLength == 1 && currentEdgeColumnRef.MaxLength == 1)
                    {
                        var isEdgeView = edgeColumn.Role == WNodeTableColumnRole.EdgeView;
                        var hasRevEdge = edgeColumn.EdgeInfo.HasReversedEdge;
                        edge = new MatchEdge
                        {
                            IsEdgeView = isEdgeView,
                            IsReversedEdge = false,
                            HasReversedEdge = hasRevEdge,
                            SourceNode = patternNode,
                            EdgeColumn = currentEdgeColumnRef,
                            EdgeAlias = edgeAlias,
                            BindNodeTableObjName = bindNodeTableObjName,
                        };
                        _context.AddEdgeReference(edge);

                        if (hasRevEdge)
                        {
                            revEdge = new MatchEdge
                            {
                                IsEdgeView = isEdgeView,
                                IsReversedEdge = true,
                                SinkNode = patternNode,
                                EdgeColumn = new WEdgeColumnReferenceExpression()
                                {
                                    ColumnType = currentEdgeColumnRef.ColumnType,
                                    Alias = currentEdgeColumnRef.Alias,
                                    MaxLength = currentEdgeColumnRef.MaxLength,
                                    MinLength = currentEdgeColumnRef.MinLength,
                                    AttributeValueDict = currentEdgeColumnRef.AttributeValueDict,
                                    MultiPartIdentifier = new WMultiPartIdentifier(new Identifier()
                                    {
                                        QuoteType = currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last().QuoteType,
                                        Value = currentRevEdgeName,
                                    }),
                                },
                                EdgeAlias = edgeAlias,
                                //EdgeAlias = revEdgeAlias,
                            };
                            reversedEdgeDict[edge.EdgeAlias] = revEdge;
                        }
                    }
                    else
                    {
                        MatchPath matchPath = new MatchPath
                        {
                            SourceNode = patternNode,
                            EdgeColumn = currentEdgeColumnRef,
                            EdgeAlias = edgeAlias,
                            BindNodeTableObjName =
                                new WSchemaObjectName(
                                    new Identifier {Value = schema},
                                    new Identifier {Value = bindTableName}
                                    ),
                            MinLength = currentEdgeColumnRef.MinLength,
                            MaxLength = currentEdgeColumnRef.MaxLength,
                            ReferencePathInfo = false,
                            AttributeValueDict = currentEdgeColumnRef.AttributeValueDict
                        };
                        _context.AddEdgeReference(matchPath);
                        pathDictionary[edgeAlias] = matchPath;
                        edge = matchPath;
                    }

                    if (preEdge != null)
                    {
                        preEdge.SinkNode = patternNode;
                        if (preEdge.HasReversedEdge)
                        {
                            //var preSourceNode = preEdge.SourceNode;
                            var preEdgeBindNodeTableObjName = preEdge.BindNodeTableObjName;
                            var preEdgeColName = preEdge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value;
                            var preEdgeColumn =
                                columnsOfNodeTables[
                                    WNamedTableReference.SchemaNameToTuple(preEdgeBindNodeTableObjName)][
                                        preEdgeColName];
                            var isEdgeView = preEdgeColumn.Role == WNodeTableColumnRole.EdgeView;
                            var isNodeView =
                                _graphMetaData.NodeViewMapping.ContainsKey(
                                    WNamedTableReference.SchemaNameToTuple(patternNode.NodeTableObjectName));

                            reversedEdgeDict[preEdge.EdgeAlias].SourceNode = patternNode;
                            // [node/nodeView]-[edgeView]->[node/nodeView]
                            if (isEdgeView)
                            {
                                reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = preEdgeBindNodeTableObjName;
                            }
                            // [node/nodeView]-[edge]->[nodeView]
                            else if (!isEdgeView && isNodeView)
                            {
                                reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = new WSchemaObjectName(
                                    new Identifier { Value = "dbo" },
                                    new Identifier { Value = preEdgeColumn.EdgeInfo.SinkNodes.First() }
                                    );
                            }
                            else
                            {
                                reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = new WSchemaObjectName(
                                    new Identifier { Value = schema },
                                    new Identifier { Value = bindTableName }
                                    );
                            }
                        }
                    }
                    preEdge = edge;

                    if (!parent.ContainsKey(currentNodeExposedName))
                        parent[currentNodeExposedName] = currentNodeExposedName;
                    if (!parent.ContainsKey(nextNodeExposedName))
                        parent[nextNodeExposedName] = nextNodeExposedName;

                    unionFind.Union(currentNodeExposedName, nextNodeExposedName);

                    patternNode.Neighbors.Add(edge);

                }
                var tailExposedName = path.Tail.BaseIdentifier.Value;
                var tailNode = nodes.GetOrCreate(tailExposedName);
                if (tailNode.NodeAlias == null)
                {
                    tailNode.NodeAlias = tailExposedName;
                    tailNode.Neighbors = new List<MatchEdge>();
                    var nodeTable = _context[tailExposedName] as WNamedTableReference;
                    if (nodeTable != null)
                    {
                        tailNode.NodeTableObjectName = nodeTable.TableObjectName;
                        if (tailNode.NodeTableObjectName.SchemaIdentifier == null)
                            tailNode.NodeTableObjectName.Identifiers.Insert(0, new Identifier {Value = "dbo"});
                    }
                }
                if (preEdge != null)
                {
                    preEdge.SinkNode = tailNode;
                    if (preEdge.HasReversedEdge)
                    {
                        var schema = tailNode.NodeTableObjectName.SchemaIdentifier.Value.ToLower();
                        var nodeTableName = tailNode.NodeTableObjectName.BaseIdentifier.Value;
                        //var preSourceNode = preEdge.SourceNode;
                        var preEdgeBindNodeTableObjName = preEdge.BindNodeTableObjName;
                        var preEdgeColName = preEdge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value;
                        var preEdgeColumn =
                            columnsOfNodeTables[
                                WNamedTableReference.SchemaNameToTuple(preEdgeBindNodeTableObjName)][
                                    preEdgeColName];
                        var isEdgeView = preEdgeColumn.Role == WNodeTableColumnRole.EdgeView;
                        var isNodeView =
                            _graphMetaData.NodeViewMapping.ContainsKey(
                                WNamedTableReference.SchemaNameToTuple(tailNode.NodeTableObjectName));
                        reversedEdgeDict[preEdge.EdgeAlias].SourceNode = tailNode;
                        // [node/nodeView]-[edgeView]->[node/nodeView]
                        if (isEdgeView)
                        {
                            reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = preEdgeBindNodeTableObjName;
                        }
                        // [node/nodeView]-[edge]->[nodeView]
                        else if (!isEdgeView && isNodeView)
                        {
                            reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = new WSchemaObjectName(
                                new Identifier { Value = "dbo" },
                                new Identifier { Value = preEdgeColumn.EdgeInfo.SinkNodes.First() }
                                );
                        }
                        else
                        {
                            reversedEdgeDict[preEdge.EdgeAlias].BindNodeTableObjName = new WSchemaObjectName(
                                new Identifier { Value = schema },
                                new Identifier { Value = nodeTableName.ToLower() }
                                );
                        }
                    }
                }
            }

            // Puts nodes into subgraphs
            foreach (var node in nodes)
            {
                string root = unionFind.Find(node.Key);
                if (!subGrpahMap.ContainsKey(root))
                {
                    var subGraph = new ConnectedComponent();
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGrpahMap[root] = subGraph;
                    connectedSubGraphs.Add(subGraph);
                    subGraph.IsTailNode[node.Value] = false;
                }
                else
                {
                    var subGraph = subGrpahMap[root];
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGraph.IsTailNode[node.Value] = false;
                }
            }

            var graph = new MatchGraph
            {
                ReversedEdgeDict = reversedEdgeDict,
                ConnectedSubGraphs = connectedSubGraphs,
                SourceNodeStatisticsDict = new Dictionary<Tuple<string, bool>, Statistics>(),
            };
            unionFind.Parent = null;

            // When an edge in the MATCH clause is not associated with an alias,
            // assigns to it a default alias: sourceAlias_EdgeColumnName_sinkAlias.
            // Also rewrites edge attributes anywhere in the query that can be bound to this default alias.
            var replaceTableRefVisitor = new ReplaceEdgeReferenceVisitor();
            replaceTableRefVisitor.Invoke(query, edgeColumnToAliasesDict);

            // Rematerializes node tables in the MATCH clause which are defined in the upper-level context
            // and join them with the upper-level table references.
            RematerilizeExtrenalNodeTableReference(query, nodes);

            // Transforms the path reference in the SELECT elements into a
            // scalar function to display path information.
            TransformPathInfoDisplaySelectElement(query, pathDictionary);

            return graph;
        }
        /// <summary>
        /// Replaces the SELECT * expression with all visible columns
        /// </summary>
        /// <param name="node"></param>
        /// <param name="graph"></param>
        private void ChangeSelectStarExpression(WSelectQueryBlock node, MatchGraph graph)
        {
            var newSelectElements = new List<WSelectElement>();
            Dictionary<string, List<WSelectElement>> starReplacement = null;
            foreach (var element in node.SelectElements)
            {
                var starElement = element as WSelectStarExpression;
                if (starElement != null)
                {
                    if (starReplacement == null)
                    {
                        starReplacement =
                            new Dictionary<string, List<WSelectElement>>(StringComparer.OrdinalIgnoreCase);
                        // Fetch table in order
                        foreach (var table in _context.NodeTableDictionary)
                        {
                            var alias = table.Key;
                            var namedTable = table.Value as WNamedTableReference;
                            if (namedTable != null)
                            {
                                foreach (
                                    var column in
                                        _graphMetaData.ColumnsOfNodeTables[
                                            WNamedTableReference.SchemaNameToTuple(namedTable.TableObjectName)].Where(
                                                e => e.Value.Role != WNodeTableColumnRole.Edge).Select(e => e.Key))
                                {
                                    var elementList = starReplacement.GetOrCreate(alias);
                                    elementList.Add(new WSelectScalarExpression
                                    {
                                        SelectExpr = new WColumnReferenceExpression
                                        {
                                            MultiPartIdentifier = new WMultiPartIdentifier
                                            {
                                                Identifiers = new List<Identifier>
                                                {
                                                    new Identifier {Value = alias},
                                                    new Identifier {Value = column}
                                                }
                                            }
                                        }

                                    });
                                }
                                if (graph == null) continue;
                                foreach (var subGraph in graph.ConnectedSubGraphs)
                                {
                                    if (subGraph.Nodes.ContainsKey(alias))
                                    {
                                        var matchNode = subGraph.Nodes[alias];
                                        foreach (var edge in matchNode.Neighbors)
                                        {
                                            var schemaName = edge.SourceNode.NodeTableObjectName.SchemaIdentifier ==
                                                             null
                                                ? "dbo"
                                                : edge.SourceNode.NodeTableObjectName.SchemaIdentifier.Value.ToLower();
                                            var nodeTuple = new Tuple<string, string>(schemaName,
                                                edge.SourceNode.NodeTableObjectName.BaseIdentifier.Value.ToLower());
                                            var edgeColumnName =
                                                edge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value.ToLower();
                                            if (!_graphMetaData.ColumnsOfNodeTables[nodeTuple].ContainsKey(edgeColumnName))
                                            {
                                                throw new GraphViewException("Invalid Edge Alias");
                                            }
                                            foreach (
                                                var column in
                                                    _graphMetaData.ColumnsOfNodeTables[nodeTuple][edgeColumnName].EdgeInfo
                                                        .ColumnAttributes)
                                            {
                                                var elementList = starReplacement.GetOrCreate(edge.EdgeAlias);
                                                elementList.Add(new WSelectScalarExpression
                                                {
                                                    SelectExpr = new WColumnReferenceExpression
                                                    {
                                                        MultiPartIdentifier = new WMultiPartIdentifier
                                                        {
                                                            Identifiers = new List<Identifier>
                                                            {
                                                                new Identifier {Value = edge.EdgeAlias},
                                                                new Identifier {Value = column}
                                                            }
                                                        }
                                                    }

                                                });
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                var derivedTable = table.Value as WQueryDerivedTable;
                                if (derivedTable == null)
                                    continue;
                                var elementList = starReplacement.GetOrCreate(alias);
                                elementList.Add(new WSelectStarExpression
                                {
                                    Qulifier = new WMultiPartIdentifier
                                    {
                                        Identifiers = new List<Identifier> {new Identifier {Value = alias}}
                                    }
                                });

                            }
                        }
                    }
                    if (starElement.Qulifier != null)
                    {
                        newSelectElements.AddRange(starReplacement[starElement.Qulifier.Identifiers.Last().Value]);
                    }
                    else
                    {
                        foreach (var value in starReplacement.Values)
                        {
                            newSelectElements.AddRange(value);
                        }
                    }
                }
                else
                {
                    newSelectElements.Add(element);
                }
            }
            if (newSelectElements.Any())
                node.SelectElements = newSelectElements;
        }
        /// <summary>
        /// Construct Graph from the match clause. The Graph can consist of multiple connected SubGraph.
        /// Not supported in this version
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        private MatchGraph ConstructGraph(WSelectQueryBlock query)
        {
            var unionFind = new UnionFind();
            if (query.MatchClause == null)
                return null;
            var edgeTableReferenceDict = new Dictionary<string, List<string>>(StringComparer.CurrentCultureIgnoreCase);
            var matchClause = query.MatchClause;
            var nodes = new Dictionary<string, MatchNode>(StringComparer.CurrentCultureIgnoreCase);
            var connectedSubGraphs = new List<ConnectedComponent>();
            var subGrpahMap = new Dictionary<string, ConnectedComponent>(StringComparer.CurrentCultureIgnoreCase);
            var parent = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
            unionFind.Parent = parent;
            HashSet<Tuple<string, string>> nodeTypes = new HashSet<Tuple<string, string>>();

            //Construct Graph from Match Pattern
            foreach (var path in matchClause.Paths)
            {
                var index = 0;
                MatchEdge preEdge = null;
                for (var count = path.PathNodeList.Count; index < count; ++index)
                {
                    var currentNode = path.PathNodeList[index].Item1;
                    var currentEdge = path.PathNodeList[index].Item2;
                    var currentNodeExposedName = currentNode.BaseIdentifier.Value;
                    var nextNode = index != count - 1
                        ? path.PathNodeList[index + 1].Item1
                        : path.Tail;
                    var nextNodeExposedName = nextNode.BaseIdentifier.Value;
                    var node = nodes.GetOrCreate(currentNodeExposedName);
                    if (node.NodeAlias == null)
                    {
                        node.NodeAlias = currentNodeExposedName;
                        node.Neighbors = new List<MatchEdge>();
                        node.External = false;
                        var nodeTable = _context[currentNodeExposedName] as WNamedTableReference;
                        if (nodeTable != null)
                        {
                            node.TableObjectName = nodeTable.TableObjectName;
                            if (node.TableObjectName.SchemaIdentifier == null)
                                node.TableObjectName.Identifiers.Insert(0, new Identifier { Value = "dbo" });
                            var nodeTypeTuple = WNamedTableReference.SchemaNameToTuple(node.TableObjectName);
                            if (!nodeTypes.Contains(nodeTypeTuple))
                                nodeTypes.Add(nodeTypeTuple);

                        }
                    }

                    if (currentEdge.AliasRole == AliasType.Default)
                    {
                        var currentEdgeName = currentEdge.MultiPartIdentifier.Identifiers.Last().Value;
                        if (edgeTableReferenceDict.ContainsKey(currentEdgeName))
                        {
                            edgeTableReferenceDict[currentEdgeName].Add(currentEdge.Alias);
                        }
                        else
                        {
                            edgeTableReferenceDict.Add(currentEdgeName, new List<string> { currentEdge.Alias });
                        }
                    }
                    var edge = new MatchEdge
                    {
                        SourceNode = node,
                        EdgeColumn = new WColumnReferenceExpression
                        {
                            MultiPartIdentifier = new WMultiPartIdentifier
                            {
                                Identifiers = new List<Identifier>
                                {
                                    new Identifier {Value = node.NodeAlias},
                                    currentEdge.MultiPartIdentifier.Identifiers.Last()
                                }
                            }
                        },
                        EdgeAlias = currentEdge.Alias
                    };

                    if (preEdge != null)
                    {
                        preEdge.SinkNode = node;
                    }
                    preEdge = edge;

                    if (!parent.ContainsKey(currentNodeExposedName))
                        parent[currentNodeExposedName] = currentNodeExposedName;
                    if (!parent.ContainsKey(nextNodeExposedName))
                        parent[nextNodeExposedName] = nextNodeExposedName;

                    unionFind.Union(currentNodeExposedName, nextNodeExposedName);


                    node.Neighbors.Add(edge);


                    _context.AddEdgeReference(currentEdge.Alias, edge.SourceNode.TableObjectName, currentEdge);
                }
                var tailExposedName = path.Tail.BaseIdentifier.Value;
                var tailNode = nodes.GetOrCreate(tailExposedName);
                if (tailNode.NodeAlias == null)
                {
                    tailNode.NodeAlias = tailExposedName;
                    tailNode.Neighbors = new List<MatchEdge>();
                    var nodeTable = _context[tailExposedName] as WNamedTableReference;
                    if (nodeTable != null)
                    {
                        tailNode.TableObjectName = nodeTable.TableObjectName;
                        if (tailNode.TableObjectName.SchemaIdentifier == null)
                            tailNode.TableObjectName.Identifiers.Insert(0, new Identifier { Value = "dbo" });
                        var nodeTypeTuple = WNamedTableReference.SchemaNameToTuple(tailNode.TableObjectName);
                        if (!nodeTypes.Contains(nodeTypeTuple))
                            nodeTypes.Add(nodeTypeTuple);
                    }
                }
                if (preEdge != null) 
                    preEdge.SinkNode = tailNode;
            }

            // Put nodes into subgraphs
            foreach (var node in nodes)
            {
                string root = unionFind.Find(node.Key);
                if (!subGrpahMap.ContainsKey(root))
                {
                    var subGraph = new ConnectedComponent();
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGrpahMap[root] = subGraph;
                    connectedSubGraphs.Add(subGraph);
                    subGraph.IsTailNode[node.Value] = false;
                }
                else
                {
                    var subGraph = subGrpahMap[root];
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGraph.IsTailNode[node.Value] = false;
                }
            }

            // Replace Edge name alias with proper alias in the query
            var replaceTableRefVisitor = new ReplaceTableRefVisitor();
            replaceTableRefVisitor.Invoke(query, edgeTableReferenceDict);
            

            // If a table alias in the MATCH clause is defined in an upper-level context, 
            // to be able to translate this MATCH clause, this table alias must be re-materialized 
            // in the FROM clause of the current context and joined with the corresponding table
            // in the upper-level context. 
            var tableRefs = query.FromClause.TableReferences;
            var tableSet = new HashSet<string>(StringComparer.CurrentCultureIgnoreCase);
            var newTableRefs = new List<WTableReference>();
            for (int index = 0; index < tableRefs.Count; ++index)
            {
                var table = tableRefs[index] as WNamedTableReference;
                if (table == null)
                {
                    newTableRefs.Add(tableRefs[index]);
                    continue;
                }
                var tableTuple = WNamedTableReference.SchemaNameToTuple(table.TableObjectName);
                if (!nodeTypes.Contains(tableTuple))
                {
                    newTableRefs.Add(table);
                }
                else
                {
                    tableSet.Add(table.ExposedName.Value);
                }
            }
            query.FromClause = new WFromClause
            {
                TableReferences = newTableRefs,
            };
            WBooleanExpression whereCondiction = null;
            foreach (var node in nodes)
            {
                if (!tableSet.Contains(node.Key))
                {
                    node.Value.External = true;
                    var newWhereCondition = new WBooleanComparisonExpression
                    {
                        ComparisonType = BooleanComparisonType.Equals,
                        FirstExpr = new WColumnReferenceExpression
                        {
                            MultiPartIdentifier = new WMultiPartIdentifier(
                                new Identifier { Value = node.Key },
                                new Identifier { Value = "GlobalNodeId" })
                        },
                        SecondExpr = new WColumnReferenceExpression
                        {
                            MultiPartIdentifier = new WMultiPartIdentifier(
                                new Identifier { Value = node.Value.RefAlias },
                                new Identifier { Value = "GlobalNodeId" })
                        },
                    };
                    whereCondiction = WBooleanBinaryExpression.Conjunction(whereCondiction, newWhereCondition);
                }
            }
            if (whereCondiction != null)
            {
                if (query.WhereClause == null)
                {
                    query.WhereClause = new WWhereClause { SearchCondition = whereCondiction };
                }
                else
                {
                    if (query.WhereClause.SearchCondition == null)
                    {
                        query.WhereClause.SearchCondition = whereCondiction;
                    }
                    else
                    {
                        query.WhereClause.SearchCondition = new WBooleanBinaryExpression
                        {
                            BooleanExpressionType = BooleanBinaryExpressionType.And,
                            FirstExpr = new WBooleanParenthesisExpression
                            {
                                Expression = query.WhereClause.SearchCondition
                            },
                            SecondExpr = new WBooleanParenthesisExpression
                            {
                                Expression = whereCondiction
                            }
                        };
                    }
                }
            }

            var graph = new MatchGraph
            {
                ConnectedSubGraphs = connectedSubGraphs,
                NodeTypesSet = nodeTypes,
            };
            unionFind.Parent = null;
            return graph;
        }
        /// <summary>
        /// Estimates the average degree of the edges and retrieve density value.
        /// Send sa query to retrieve the varbinary of the sink in the edge sampling table with edge predicates,
        /// then generates the statistics histogram for each edge
        /// </summary>
        private void RetrieveStatistics(MatchGraph graph)
        {
            if (graph == null) throw new ArgumentNullException("graph");
            // Declare the parameters if any
            var declareParameter = "";
            if (_variables != null)
            {
                declareParameter = _variables.Aggregate(declareParameter,
                    (current, parameter) =>
                        current +
                        ("DECLARE " + parameter.VariableName.Value + " " +
                         TsqlFragmentToString.DataType(parameter.DataType) + "\r\n"));
            }

            // Calculates the average degree
            var sb = new StringBuilder();
            bool first = true;
            sb.Append("SELECT [Edge].*, [EdgeDegrees].[SampleRowCount], [EdgeDegrees].[AverageDegree] FROM");
            sb.Append("(\n");
            foreach (var edge in graph.ConnectedSubGraphs.SelectMany(subGraph => subGraph.Edges.Values))
            {
                if (!first)
                    sb.Append("\nUNION ALL\n");
                else
                {
                    first = false;
                }
                var tableObjectName = edge.SourceNode.NodeTableObjectName;
                string schema = tableObjectName.SchemaIdentifier.Value.ToLower();
                string tableName = tableObjectName.BaseIdentifier.Value.ToLower();
                string edgeName = edge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value.ToLower();
                string bindTableName = _context.EdgeNodeBinding[new Tuple<string, string>(tableName, edgeName)];

                // Distinguished between path and edge
                //var sinkColumnName = edge.IsPath ? "COUNT(Sink)" : "[dbo].[GraphViewUDFGlobalNodeIdEncoder](Sink)";
                sb.Append(
                    string.Format(@"
                            SELECT '{0}' as TableSchema,
                                   '{1}' as TableName,
                                   '{2}' as ColumnName,
                                   '{3}' as Alias,
                                   [dbo].[GraphViewUDFGlobalNodeIdEncoder](Src) as Src,
                                   [dbo].[GraphViewUDFGlobalNodeIdEncoder](Sink) as Sink,
                                   0 as IsReversed,
                                   NULL as OriginalEdgeAlias
                            FROM [{0}_{1}_{2}_Sampling] as [{3}]", schema,
                        bindTableName,
                        edgeName,
                        edge.EdgeAlias));
                var predicatesExpr = edge.RetrievePredicatesExpression();
                if (predicatesExpr!=null)
                    sb.AppendFormat("\n WHERE {0}", predicatesExpr);

                MatchEdge revEdge;
                if (graph.ReversedEdgeDict.TryGetValue(edge.EdgeAlias, out revEdge))
                {
                    sb.Append("\nUNION ALL\n");
                    var isEdgeView = revEdge.IsEdgeView;
                    var revTableObjectName = revEdge.SourceNode.NodeTableObjectName;
                    var revSchema = revTableObjectName.SchemaIdentifier.Value.ToLower();
                    var revEdgeName = revEdge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value.ToLower();
                    var revBindTableName = revEdge.BindNodeTableObjName.Identifiers.Last().Value.ToLower();
                    var revSamplingTableName = revSchema + "_" + revBindTableName + "_" + revEdgeName;
                    var revSrcTuple = WNamedTableReference.SchemaNameToTuple(revEdge.BindNodeTableObjName);
                    //var revSinkTuple = WNamedTableReference.SchemaNameToTuple(revEdge.SinkNode.NodeTableObjectName);
                    var originalEdgeName = edge.EdgeColumn.MultiPartIdentifier.Identifiers.Last().Value.ToLower();
                    // [node/nodeView]-[edgeView]->[node/nodeView]
                    if (isEdgeView)
                    {
                        revEdgeName = revSrcTuple.Item2 + "_" + originalEdgeName + "Reversed";
                        revSamplingTableName = revSrcTuple.Item1 + "_" + revSrcTuple.Item2 + "_" + revEdgeName;
                    }

                    sb.Append(
                        string.Format(@"
                                SELECT '{0}' as TableSchema,
                                       '{1}' as TableName,
                                       '{2}' as ColumnName,
                                       '{3}' as Alias,
                                        [dbo].[GraphViewUDFGlobalNodeIdEncoder](Src) as Src,
                                        [dbo].[GraphViewUDFGlobalNodeIdEncoder](Sink) as Sink,
                                        1 as IsReversed,
                                       '{4}' as OriginalEdgeAlias
                                FROM [{5}_Sampling] as [{3}]", revSchema,
                            revBindTableName,
                            revEdgeName,
                            revEdge.EdgeAlias,
                            edge.EdgeAlias,
                            revSamplingTableName));
                    var revPredicatesExpr = revEdge.RetrievePredicatesExpression();
                    if (revPredicatesExpr != null)
                        sb.AppendFormat("\n WHERE {0}", revPredicatesExpr);
                }
            }
            sb.Append("\n) as Edge \n");
            sb.Append(String.Format(@"
                        INNER JOIN
                            [{0}] as [EdgeDegrees]
                        ON
                            [EdgeDegrees].[TableSchema] = [Edge].[TableSchema]
                        AND [EdgeDegrees].[TableName] = [Edge].[TableName]
                        AND [EdgeDegrees].[ColumnName] = [Edge].[ColumnName]", GraphViewConnection.MetadataTables[3]));

            using (var command = Tx.Connection.CreateCommand())
            {
                command.Transaction = Tx;
                command.CommandText = declareParameter + sb.ToString();
                using (var reader = command.ExecuteReader())
                {
                    var srcNodeStatisticsDict = graph.SourceNodeStatisticsDict;
                    while (reader.Read())
                    {
                        MatchEdge edge = null;
                        bool isReversed = reader["isReversed"].ToString().Equals("1");
                        if (!isReversed && !graph.TryGetEdge(reader["Alias"].ToString(), out edge))
                            throw new GraphViewException(string.Format("Edge {0} not exists", reader["Alias"].ToString()));
                        if (isReversed && !graph.TryGetEdge(reader["OriginalEdgeAlias"].ToString(), out edge))
                            throw new GraphViewException(string.Format("Edge {0} not exists", reader["OriginalEdgeAlias"].ToString()));
                        if (isReversed)
                            edge = graph.ReversedEdgeDict[edge.EdgeAlias];

                        var srcBytes = reader["Src"] as byte[];
                        var cursor = 0;
                        if (srcBytes != null)
                        {
                            var srcList = new List<long>();
                            while (cursor < srcBytes.Length)
                            {
                                var src = BitConverter.ToInt64(srcBytes, cursor);
                                cursor += 8;
                                srcList.Add(src);
                            }
                            var tmpEdge = new MatchEdge();
                            Statistics.UpdateEdgeHistogram(tmpEdge, srcList);
                            srcNodeStatisticsDict[new Tuple<string, bool>(edge.EdgeAlias, isReversed)] = tmpEdge.Statistics;
                        }
                        else
                            srcNodeStatisticsDict[new Tuple<string, bool>(edge.EdgeAlias, isReversed)] = null;

                        var sinkBytes = reader["Sink"] as byte[];
                        if (sinkBytes == null)
                        {
                            edge.Statistics = new Statistics
                            {
                                Density = 0,
                                Histogram = new Dictionary<long, Tuple<double, bool>>(),
                                MaxValue = 0,
                                RowCount = 0,
                                //Selectivity = 1.0
                            };
                            continue;
                        }
                        List<long> sinkList = new List<long>();
                        cursor = 0;
                        while (cursor < sinkBytes.Length)
                        {
                            var sink = BitConverter.ToInt64(sinkBytes, cursor);
                            cursor += 8;
                            sinkList.Add(sink);
                        }
                        Statistics.UpdateEdgeHistogram(edge, sinkList);
                        edge.AverageDegree = Convert.ToDouble(reader["AverageDegree"])*sinkList.Count*1.0/
                                             Convert.ToInt64(reader["SampleRowCount"]);
                        var path = edge as MatchPath;
                        if (path != null)
                        {
                            if (path.AverageDegree > 1)
                                if (path.MaxLength != -1)
                                {
                                    path.AverageDegree = Math.Pow(path.AverageDegree, path.MaxLength) -
                                                         (path.MinLength > 0
                                                             ? Math.Pow(path.AverageDegree, path.MinLength - 1)
                                                             : 0);
                                }
                                else
                                    path.AverageDegree = double.MaxValue;
                        }

                    }
                }

                // Retrieves density value for each node table
                string tempTableName = Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
                var dbccDensityQuery = new StringBuilder();
                dbccDensityQuery.Append(string.Format(@"CREATE TABLE #{0} (Density float, Len int, Col sql_variant);
                                                    INSERT INTO #{0} EXEC('", tempTableName));
                Dictionary<Tuple<string, string>, List<MatchNode>> schemaTableToNodeListMapping =
                    new Dictionary<Tuple<string, string>, List<MatchNode>>();
                foreach (var subGraph in graph.ConnectedSubGraphs)
                {
                    foreach (var node in subGraph.Nodes.Values)
                    {
                        var tableTuple = WNamedTableReference.SchemaNameToTuple(node.NodeTableObjectName);
                        if (_graphMetaData.NodeViewMapping.ContainsKey(tableTuple))
                        {
                            node.GlobalNodeIdDensity = Statistics.DefaultDensity;
                        }
                        else
                        {
                            var nodeList = schemaTableToNodeListMapping.GetOrCreate(tableTuple);
                            nodeList.Add(node);
                        }

                    }
                }
                foreach (var tableTuple in schemaTableToNodeListMapping.Keys)
                {
                    dbccDensityQuery.Append(string.Format(
                        "DBCC SHOW_STATISTICS (\"{0}.{1}\", [{0}{1}_PK_GlobalNodeId]) with DENSITY_VECTOR;\n",
                        tableTuple.Item1,
                        tableTuple.Item2));
                }
                dbccDensityQuery.Append("');\n");
                dbccDensityQuery.Append(string.Format("SELECT Density FROM #{0} WHERE Col = 'GlobalNodeId'", tempTableName));
                command.CommandText = dbccDensityQuery.ToString();
                using (var reader = command.ExecuteReader())
                {
                    foreach (var item in schemaTableToNodeListMapping)
                    {
                        double density;
                        if (!reader.Read())
                            density = Statistics.DefaultDensity;
                        else
                        {
                            density = Convert.ToDouble(reader["Density"]);
                            if (Math.Abs(density - 1.0) < 0.0001)
                                density = Statistics.DefaultDensity;
                        }

                        foreach (var node in item.Value)
                        {
                            node.GlobalNodeIdDensity = density;
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Constructs the graph pattern specified by the MATCH clause. 
        /// The graph pattern may consist of multiple fully-connected sub-graphs.
        /// </summary>
        /// <param name="query">The SELECT query block</param>
        /// <returns>A graph object contains all the connected componeents</returns>
        private MatchGraph ConstructGraph(WSelectQueryBlock query)
        {
            if (query == null || query.MatchClause == null)
                return null;

            var unionFind = new UnionFind();
            var edgeColumnToAliasesDict = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
            var pathDictionary = new Dictionary<string, MatchPath>(StringComparer.OrdinalIgnoreCase);
            var matchClause = query.MatchClause;
            var nodes = new Dictionary<string, MatchNode>(StringComparer.OrdinalIgnoreCase);
            var connectedSubGraphs = new List<ConnectedComponent>();
            var subGrpahMap = new Dictionary<string, ConnectedComponent>(StringComparer.OrdinalIgnoreCase);
            var parent = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            unionFind.Parent = parent;

            // Constructs the graph pattern specified by the path expressions in the MATCH clause
            foreach (var path in matchClause.Paths)
            {
                var index = 0;
                MatchEdge preEdge = null;
                for (var count = path.PathEdgeList.Count; index < count; ++index)
                {
                    var currentNodeTableRef = path.PathEdgeList[index].Item1;
                    var currentEdgeColumnRef = path.PathEdgeList[index].Item2;
                    var currentNodeExposedName = currentNodeTableRef.BaseIdentifier.Value;
                    var nextNodeTableRef = index != count - 1
                        ? path.PathEdgeList[index + 1].Item1
                        : path.Tail;
                    var nextNodeExposedName = nextNodeTableRef.BaseIdentifier.Value;
                    var patternNode = nodes.GetOrCreate(currentNodeExposedName);
                    if (patternNode.NodeAlias == null)
                    {
                        patternNode.NodeAlias = currentNodeExposedName;
                        patternNode.Neighbors = new List<MatchEdge>();
                        patternNode.External = false;
                        var nodeTable = _context[currentNodeExposedName] as WNamedTableReference;
                        if (nodeTable != null)
                        {
                            patternNode.NodeTableObjectName = nodeTable.TableObjectName;
                            if (patternNode.NodeTableObjectName.SchemaIdentifier == null)
                                patternNode.NodeTableObjectName.Identifiers.Insert(0, new Identifier {Value = "dbo"});
                        }
                    }

                    string edgeAlias = currentEdgeColumnRef.Alias;
                    if (edgeAlias == null)
                    {
                        var currentEdgeName = currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last().Value;
                        edgeAlias = string.Format("{0}_{1}_{2}", currentNodeExposedName, currentEdgeName,
                            nextNodeExposedName);
                        if (edgeColumnToAliasesDict.ContainsKey(currentEdgeName))
                        {
                            edgeColumnToAliasesDict[currentEdgeName].Add(edgeAlias);
                        }
                        else
                        {
                            edgeColumnToAliasesDict.Add(currentEdgeName, new List<string> { edgeAlias });
                        }
                    }

                    Identifier edgeIdentifier = currentEdgeColumnRef.MultiPartIdentifier.Identifiers.Last();
                    string schema = patternNode.NodeTableObjectName.SchemaIdentifier.Value.ToLower();
                    string nodeTableName = patternNode.NodeTableObjectName.BaseIdentifier.Value;
                    string bindTableName =
                        _context.EdgeNodeBinding[
                            new Tuple<string, string>(nodeTableName.ToLower(), edgeIdentifier.Value.ToLower())].ToLower();
                    MatchEdge edge;
                    if (currentEdgeColumnRef.MinLength == 1 && currentEdgeColumnRef.MaxLength == 1)
                    {
                        edge = new MatchEdge
                        {
                            SourceNode = patternNode,
                            EdgeColumn = currentEdgeColumnRef,
                            EdgeAlias = edgeAlias,
                            BindNodeTableObjName =
                                new WSchemaObjectName(
                                    new Identifier {Value = schema},
                                    new Identifier {Value = bindTableName}
                                    ),
                        };
                        _context.AddEdgeReference(edge);
                    }
                    else
                    {
                        MatchPath matchPath = new MatchPath
                        {
                            SourceNode = patternNode,
                            EdgeColumn = currentEdgeColumnRef,
                            EdgeAlias = edgeAlias,
                            BindNodeTableObjName =
                                new WSchemaObjectName(
                                    new Identifier {Value = schema},
                                    new Identifier {Value = bindTableName}
                                    ),
                            MinLength = currentEdgeColumnRef.MinLength,
                            MaxLength = currentEdgeColumnRef.MaxLength,
                            ReferencePathInfo = false,
                            AttributeValueDict = currentEdgeColumnRef.AttributeValueDict
                        };
                        _context.AddEdgeReference(matchPath);
                        pathDictionary[edgeAlias] = matchPath;
                        edge = matchPath;
                    }

                    if (preEdge != null)
                    {
                        preEdge.SinkNode = patternNode;
                    }
                    preEdge = edge;

                    if (!parent.ContainsKey(currentNodeExposedName))
                        parent[currentNodeExposedName] = currentNodeExposedName;
                    if (!parent.ContainsKey(nextNodeExposedName))
                        parent[nextNodeExposedName] = nextNodeExposedName;

                    unionFind.Union(currentNodeExposedName, nextNodeExposedName);


                    patternNode.Neighbors.Add(edge);


                }
                var tailExposedName = path.Tail.BaseIdentifier.Value;
                var tailNode = nodes.GetOrCreate(tailExposedName);
                if (tailNode.NodeAlias == null)
                {
                    tailNode.NodeAlias = tailExposedName;
                    tailNode.Neighbors = new List<MatchEdge>();
                    var nodeTable = _context[tailExposedName] as WNamedTableReference;
                    if (nodeTable != null)
                    {
                        tailNode.NodeTableObjectName = nodeTable.TableObjectName;
                        if (tailNode.NodeTableObjectName.SchemaIdentifier == null)
                            tailNode.NodeTableObjectName.Identifiers.Insert(0, new Identifier {Value = "dbo"});
                    }
                }
                if (preEdge != null)
                    preEdge.SinkNode = tailNode;
            }

            // Puts nodes into subgraphs
            foreach (var node in nodes)
            {
                string root = unionFind.Find(node.Key);
                if (!subGrpahMap.ContainsKey(root))
                {
                    var subGraph = new ConnectedComponent();
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGrpahMap[root] = subGraph;
                    connectedSubGraphs.Add(subGraph);
                    subGraph.IsTailNode[node.Value] = false;
                }
                else
                {
                    var subGraph = subGrpahMap[root];
                    subGraph.Nodes[node.Key] = node.Value;
                    foreach (var edge in node.Value.Neighbors)
                    {
                        subGraph.Edges[edge.EdgeAlias] = edge;
                    }
                    subGraph.IsTailNode[node.Value] = false;
                }
            }

            var graph = new MatchGraph
            {
                ConnectedSubGraphs = connectedSubGraphs,
            };
            unionFind.Parent = null;

            // When an edge in the MATCH clause is not associated with an alias, 
            // assigns to it a default alias: sourceAlias_EdgeColumnName_sinkAlias. 
            // Also rewrites edge attributes anywhere in the query that can be bound to this default alias. 
            var replaceTableRefVisitor = new ReplaceEdgeReferenceVisitor();
            replaceTableRefVisitor.Invoke(query, edgeColumnToAliasesDict);

            // Rematerializes node tables in the MATCH clause which are defined in the upper-level context
            // and join them with the upper-level table references.
            RematerilizeExtrenalNodeTableReference(query, nodes);

            // Transforms the path reference in the SELECT elements into a 
            // scalar function to display path information.
            TransformPathInfoDisplaySelectElement(query, pathDictionary);

            
            return graph;
        }