Example #1
0
        /// <summary>
        /// Parses a CREATE TABLE statement. The parser first replaces column annotations with white space, 
        /// then uses T-SQL parser to parse it, and finally interprets the column annotations.
        /// </summary>
        /// <param name="queryStr">The CREATE TABLE statement creating a ndoe table</param>
        /// <param name="nodeTableColumns">A list of columns of the node table</param>
        /// <param name="errors">Parsing errors</param>
        /// <returns>The syntax tree of the CREATE TABLE statement</returns>
        public WSqlFragment ParseCreateNodeTableStatement(
            string queryStr, 
            out List<WNodeTableColumn> nodeTableColumns, 
            out IList<ParseError> errors)
        {
            // Gets token stream
            var tsqlParser = new TSql110Parser(true);
            var sr = new StringReader(queryStr);
            var tokens = new List<TSqlParserToken>(tsqlParser.GetTokenStream(sr, out errors));
            if (errors.Count > 0)
            {
                nodeTableColumns = null;
                return null;
            }

            // Retrieves node table columns
            var currentToken = 0;
            var farestError = 0;
            nodeTableColumns = new List<WNodeTableColumn>();
            while (currentToken < tokens.Count)
            {
                WNodeTableColumn column = null;
                if (ParseNodeTableColumn(tokens, ref currentToken, ref column, ref farestError))
                    nodeTableColumns.Add(column);
                else
                    currentToken++;
            }

            // Replaces column annotations with whitespace
            foreach (var t in nodeTableColumns)
            {
                tokens[t.FirstTokenIndex].TokenType = TSqlTokenType.WhiteSpace;
                tokens[t.FirstTokenIndex].Text = "";
            }

            // Parses the remaining statement using the T-SQL parser
            //IList<ParseError> errors;
            var parser = new WSqlParser();
            var fragment = parser.Parse(tokens, out errors) as WSqlScript;
            if (errors.Count > 0)
                return null;

            // In addition to columns specified in the CREATE TABLE statement,
            // adds an additional column recording the incoming degree of nodes.
            var inDegreeCol = new WColumnDefinition
            {
                ColumnIdentifier = new Identifier { Value = "InDegree" },
                Constraints = new List<WConstraintDefinition>{new WNullableConstraintDefinition { Nullable = false }},
                DataType = new WParameterizedDataTypeReference
                {
                    Name = new WSchemaObjectName(new Identifier { Value = "int" }),
                },
                DefaultConstraint = new WDefaultConstraintDefinition
                {
                    Expression = new WValueExpression
                    {
                        Value = "0"
                    }
                }
            };

            var deltaColumnDefList = new List<WColumnDefinition>();

            
            WCreateTableStatement stmt = fragment.Batches[0].Statements[0] as WCreateTableStatement;
            if (stmt == null || stmt.Definition == null || stmt.Definition.ColumnDefinitions==null)
            {
                return null;
            }
            else if (stmt.Definition.ColumnDefinitions.Count != nodeTableColumns.Count)
            {
                var error = tokens[stmt.FirstTokenIndex];
                errors.Add(new ParseError(0, error.Offset, error.Line, error.Column,
                    "Metadata should be specified for each column when creating a node table"));
            }
            

            var graphColIndex = 0;
            var rawColumnDef = stmt.Definition.ColumnDefinitions;
            for (var i = 0; i < rawColumnDef.Count && graphColIndex < nodeTableColumns.Count; ++i, ++graphColIndex)
            {
                var nextGraphColumn = nodeTableColumns[graphColIndex];
                // Skips columns without annotations
                while (i < rawColumnDef.Count && rawColumnDef[i].LastTokenIndex < nextGraphColumn.FirstTokenIndex)
                {
                    ++i;
                }

                switch (nextGraphColumn.ColumnRole)
                {
                    case WNodeTableColumnRole.Edge:
                        // For an adjacency-list column, its data type is always varbinary(max)
                        var def = rawColumnDef[i];
                        def.DataType = new WParameterizedDataTypeReference
                        {
                            Name = new WSchemaObjectName(new Identifier { Value = "varbinary" }),
                            Parameters = new List<Literal> { new MaxLiteral { Value = "max" } }
                        };
                        def.Constraints.Add(new WNullableConstraintDefinition { Nullable = false });
                        def.DefaultConstraint = new WDefaultConstraintDefinition
                        {
                            Expression = new WValueExpression
                            {
                                Value = "0x"
                            }
                        };
                        // For each adjacency-list column, adds a "delta" column to 
                        // facilitate deleting edges.
                        deltaColumnDefList.Add(new WColumnDefinition
                        {
                            ColumnIdentifier = new Identifier { Value = def.ColumnIdentifier.Value + "DeleteCol" },
                            ComputedColumnExpression = def.ComputedColumnExpression,
                            Constraints = def.Constraints,
                            DataType = def.DataType,
                            DefaultConstraint = def.DefaultConstraint,
                        });
                        // For each adjacency-list column, adds an integer column to record the list's outgoing degree
                        deltaColumnDefList.Add(new WColumnDefinition
                        {
                            ColumnIdentifier = new Identifier { Value = def.ColumnIdentifier.Value + "OutDegree" },
                            Constraints = def.Constraints,
                            DataType = new WParameterizedDataTypeReference
                            {
                                Name = new WSchemaObjectName(new Identifier { Value = "int" }),
                            },
                            DefaultConstraint = new WDefaultConstraintDefinition
                            {
                                Expression = new WValueExpression
                                {
                                    Value = "0"
                                }
                            }


                        });
                        break;
                    case WNodeTableColumnRole.NodeId:
                        // set unique key to user defined node id
                        bool containNullableConstraint = false;
                        foreach (var con in rawColumnDef[i].Constraints)
                        {
                            var nullableConstraint = con as WNullableConstraintDefinition;
                            if (nullableConstraint != null)
                            {
                                containNullableConstraint = true;
                                nullableConstraint.Nullable = false;
                                break;
                            }
                        }
                        if (!containNullableConstraint)
                        {
                            rawColumnDef[i].Constraints.Add(new WNullableConstraintDefinition { Nullable = false });
                        }
                        rawColumnDef[i].Constraints.Add(new WUniqueConstraintDefinition
                        {
                            Clustered = false,
                            IsPrimaryKey = false,
                        });
                        break;
                }
            }

            // Adds a GlobalNodeID column to the node table.
            // This column is the primary key of the node table. 
            var globalNodeIdCol = new WColumnDefinition
            {
                ColumnIdentifier = new Identifier { Value = "GlobalNodeId" },
                DataType = new WParameterizedDataTypeReference
                {
                    Name = new WSchemaObjectName(new Identifier { Value = "bigint" }),
                },
                Constraints = new List<WConstraintDefinition>
                    {
                        new WUniqueConstraintDefinition
                        {
                            Clustered = true,
                            IsPrimaryKey = true,
                            ConstraintIdentifier =
                                new Identifier
                                {
                                    Value =
                                        (stmt.SchemaObjectName.SchemaIdentifier == null
                                            ? "dbo"
                                            : stmt.SchemaObjectName.SchemaIdentifier.Value) +
                                        stmt.SchemaObjectName.BaseIdentifier.Value + "_PK_GlobalNodeId"
                                }
                        }
                    },
                IdentityOptions = new WIdentityOptions
                {
                    IdentitySeed = new WValueExpression("1", false),
                    IdentityIncrement = new WValueExpression("1", false),
                },
            };

            // Adds an identity column to the node table. 
            // This column will be used to adjust size estimation. 
            var identityCol = new WColumnDefinition
            {
                ColumnIdentifier = new Identifier { Value = "LocalNodeId" },
                DataType = new WParameterizedDataTypeReference
                {
                    Name = new WSchemaObjectName(new Identifier { Value = "int" }),
                },
                DefaultConstraint = new WDefaultConstraintDefinition
                {
                    Expression = new WFunctionCall
                    {
                        FunctionName = new Identifier { Value = "CHECKSUM" },
                        Parameters = new List<WScalarExpression>
                            {
                                new WFunctionCall
                                {
                                    FunctionName = new Identifier{Value = "NEWID"},
                                    Parameters = new List<WScalarExpression>()
                                }
                            }
                    }
                }

            };

            
            foreach (var definition in deltaColumnDefList)
            {
                stmt.Definition.ColumnDefinitions.Add(definition);
            }
            stmt.Definition.ColumnDefinitions.Add(globalNodeIdCol);
            stmt.Definition.ColumnDefinitions.Add(identityCol);
            stmt.Definition.ColumnDefinitions.Add(inDegreeCol);

            return fragment;
        }
Example #2
0
        /// <summary>
        /// Parses a GraphView query into a syntax tree. The parser re-uses the T-SQL parser by 
        /// masking graph-extended query constructs with comments first and then putting them back  
        /// into the syntax tree. 
        /// </summary>
        /// <param name="queryInput">The query string</param>
        /// <param name="errors">A list of parsing errors</param>
        /// <returns>The syntax tree of the input query</returns>
        public WSqlFragment Parse(TextReader queryInput, out IList<ParseError> errors)
        {
            var parser = new WSqlParser();
            //IList<ParseError> tokenParseErrors;
            _tokens = new List<TSqlParserToken>(parser.tsqlParser.GetTokenStream(queryInput, out errors));
            if (errors.Count > 0)
                return null;

            // Removes comments
            _tokens.RemoveAll(x => x.TokenType == TSqlTokenType.MultilineComment);
            _tokens.RemoveAll(x => x.TokenType == TSqlTokenType.SingleLineComment);
            var annotations = FindReplaceGraphModificationStatements(ref errors);
            ExtractMatchClause();

            // Parses the transformed script into a standard SQL syntax tree
            // using the T-SQL parser
            var script = parser.Parse(_tokens, out errors) as WSqlScript;
            if (errors.Count > 0)
                return null;
            // Converts data modification statements back to graph modification statements,
            // if they are artificial products of graph modification statements.  
            var convertStatmentVisitor = new ConvertToModificationStatementVisitor();
            convertStatmentVisitor.Invoke(script,annotations);
            if (script == null)
                return null;
            // Puts the MATCH clause(s) into the syntax tree
            var matchClauseVisitor = new MatchClauseVisitor
            {
                MatchList = _matchList,
                MatchFlag = _matchFlag,
                Tokens = _tokens
            };
            matchClauseVisitor.Invoke(script, ref errors);
            if (errors.Count > 0)
                return null;
                
            return script;
        }
Example #3
0
        /// <summary>
        /// Parses a ALTER TABLE DROP COLUMN statement.  
        /// </summary>
        /// <param name="queryStr">The CREATE TABLE statement creating a ndoe table</param>
        /// <param name="errors">Parsing errors</param>
        /// <returns>The syntax tree of the CREATE TABLE statement</returns>
        public WSqlFragment ParseAlterTableDropNodeTableColumnStatement(
            string queryStr,
            out IList<ParseError> errors)
        {
            // Gets token stream
            var tsqlParser = new TSql110Parser(true);
            var sr = new StringReader(queryStr);
            var tokens = new List<TSqlParserToken>(tsqlParser.GetTokenStream(sr, out errors));
            if (errors.Count > 0)
                return null;

            // Parses the remaining statement using the T-SQL parser
            //IList<ParseError> errors;
            var parser = new WSqlParser();
            var fragment = parser.Parse(tokens, out errors) as WSqlScript;
            if (errors.Count > 0)
                return null;

            return fragment;
        }
Example #4
0
        public WSqlFragment ParseCreateNodeEdgeViewStatement(string query, out IList<ParseError> errors)
        {
            var tsqlParser = new TSql110Parser(true);
            var sr = new StringReader(query);
            var tokens = new List<TSqlParserToken>(tsqlParser.GetTokenStream(sr, out errors));
            if (errors.Count > 0)
            {
                return null;
            }
            int currentToken = 0;
            int farestError = 0;
            while (currentToken < tokens.Count)
            {
                int nextToken = currentToken;
                if (ReadToken(tokens, "create", ref nextToken, ref farestError))
                {
                    int pos = nextToken;
                    if (ReadToken(tokens, "node", ref nextToken, ref farestError))
                    {
                        tokens[pos].TokenType = TSqlTokenType.MultilineComment;
                        tokens[pos].Text = "/*__GRAPHVIEW_CREATE_NODEVIEW*/";
                    }
                    else if (ReadToken(tokens, "edge", ref nextToken, ref farestError))
                    {
                        tokens[pos].TokenType = TSqlTokenType.MultilineComment;
                        tokens[pos].Text = "/*__GRAPHVIEW_CREATE_EDGEVIEW*/";
                    }
                    else
                    {
                        var error = tokens[farestError];
                        throw new SyntaxErrorException(error.Line, error.Text);
                        //errors.Add(new ParseError(0, error.Offset, error.Line, error.Column,
                        //    string.Format("Incorrect syntax near {0}", error.Text)));
                    }
                }
                currentToken++;
            }

            var parser = new WSqlParser();
            var fragment = parser.Parse(tokens, out errors) as WSqlScript;
            if (errors.Count > 0)
                return null;
            return fragment;
        }
Example #5
0
        /// <summary>
        /// Parses a ALTER TABLE ADD PROPERTY/EDGE statement. The parser first replaces column annotations with white space, 
        /// then uses T-SQL parser to parse it, and finally interprets the column annotations.
        /// </summary>
        /// <param name="queryStr">The CREATE TABLE statement creating a ndoe table</param>
        /// <param name="nodeTableColumns">A list of columns of the node table</param>
        /// <param name="errors">Parsing errors</param>
        /// <returns>The syntax tree of the CREATE TABLE statement</returns>
        public WSqlFragment ParseAlterTableAddNodeTableColumnStatement(
            string queryStr,
            out List<WNodeTableColumn> nodeTableColumns,
            out IList<ParseError> errors)
        {
            // Gets token stream
            var tsqlParser = new TSql110Parser(true);
            var sr = new StringReader(queryStr);
            var tokens = new List<TSqlParserToken>(tsqlParser.GetTokenStream(sr, out errors));
            if (errors.Count > 0)
            {
                nodeTableColumns = null;
                return null;
            }

            // Retrieves node table columns
            var currentToken = 0;
            var farestError = 0;
            nodeTableColumns = new List<WNodeTableColumn>();
            while (currentToken < tokens.Count)
            {
                WNodeTableColumn column = null;
                if (ParseNodeTableColumn(tokens, ref currentToken, ref column, ref farestError))
                    nodeTableColumns.Add(column);
                else
                    currentToken++;
            }

            // Replaces column annotations with whitespace
            foreach (var t in nodeTableColumns)
            {
                tokens[t.FirstTokenIndex].TokenType = TSqlTokenType.WhiteSpace;
                tokens[t.FirstTokenIndex].Text = "";
            }

            // Parses the remaining statement using the T-SQL parser
            //IList<ParseError> errors;
            var parser = new WSqlParser();
            var fragment = parser.Parse(tokens, out errors) as WSqlScript;
            if (errors.Count > 0)
                return null;

            var deltaColumnDefList = new List<WColumnDefinition>();
            var stmt = fragment.Batches[0].Statements[0] as WAlterTableAddTableElementStatement;
            if (stmt == null || stmt.Definition == null || stmt.Definition.ColumnDefinitions == null)
            {
                return null;
            }
            else if (stmt.Definition.ColumnDefinitions.Count != nodeTableColumns.Count)
            {
                var error = tokens[stmt.FirstTokenIndex];
                errors.Add(new ParseError(0, error.Offset, error.Line, error.Column,
                    "Metadata should be specified for each column when altering a node table"));
            }

            var graphColIndex = 0;
            var rawColumnDef = stmt.Definition.ColumnDefinitions;
            for (var i = 0; i < rawColumnDef.Count && graphColIndex < nodeTableColumns.Count; ++i, ++graphColIndex)
            {
                var nextGraphColumn = nodeTableColumns[graphColIndex];
                // Skips columns without annotations
                while (i < rawColumnDef.Count && rawColumnDef[i].LastTokenIndex < nextGraphColumn.FirstTokenIndex)
                {
                    ++i;
                }

                switch (nextGraphColumn.ColumnRole)
                {
                    case WNodeTableColumnRole.Edge:
                        // For an adjacency-list column, its data type is always varbinary(max)
                        var def = rawColumnDef[i];
                        def.DataType = new WParameterizedDataTypeReference
                        {
                            Name = new WSchemaObjectName(new Identifier { Value = "varbinary" }),
                            Parameters = new List<Literal> { new MaxLiteral { Value = "max" } }
                        };
                        def.Constraints.Add(new WNullableConstraintDefinition { Nullable = false });
                        def.DefaultConstraint = new WDefaultConstraintDefinition
                        {
                            Expression = new WValueExpression
                            {
                                Value = "0x"
                            }
                        };
                        // For each adjacency-list column, adds a "delta" column to
                        // facilitate deleting edges.
                        deltaColumnDefList.Add(new WColumnDefinition
                        {
                            ColumnIdentifier = new Identifier { Value = def.ColumnIdentifier.Value + "DeleteCol" },
                            ComputedColumnExpression = def.ComputedColumnExpression,
                            Constraints = def.Constraints,
                            DataType = def.DataType,
                            DefaultConstraint = def.DefaultConstraint,
                        });
                        // For each adjacency-list column, adds an integer column to record the list's outgoing degree
                        deltaColumnDefList.Add(new WColumnDefinition
                        {
                            ColumnIdentifier = new Identifier {Value = def.ColumnIdentifier.Value + "OutDegree"},
                            Constraints = def.Constraints,
                            DataType = new WParameterizedDataTypeReference
                            {
                                Name = new WSchemaObjectName(new Identifier {Value = "int"}),
                            },
                            DefaultConstraint = new WDefaultConstraintDefinition
                            {
                                Expression = new WValueExpression
                                {
                                    Value = "0"
                                }
                            }
                        });
                        break;
                    case WNodeTableColumnRole.Property:
                        break;
                    default:
                        var error = tokens[nextGraphColumn.FirstTokenIndex];
                        errors.Add(new ParseError(0, error.Offset, error.Line, error.Column,
                            "Only edge or property can be added to the node table"));
                        break;
                }
            }

            foreach (var definition in deltaColumnDefList)
                stmt.Definition.ColumnDefinitions.Add(definition);

            return fragment;
        }