/// <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; }
/// <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; }
/// <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; }
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; }
/// <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; }