private WColumnDefinition ParseColumnDefinition(ColumnDefinition columnDef) { if (columnDef == null) return null; var wColumnDef = new WColumnDefinition { FirstTokenIndex = columnDef.FirstTokenIndex, LastTokenIndex = columnDef.LastTokenIndex, ColumnIdentifier = columnDef.ColumnIdentifier, DataType = ParseDataType(columnDef.DataType), Collation = columnDef.Collation, ComputedColumnExpression = ParseScalarExpression(columnDef.ComputedColumnExpression), StorageOptions = columnDef.StorageOptions, Index = ParseIndexDefinition(columnDef.Index), }; if (columnDef.Constraints != null) { wColumnDef.Constraints = new List<WConstraintDefinition>(columnDef.Constraints.Count); foreach (var con in columnDef.Constraints) wColumnDef.Constraints.Add(ParseConstraintDefinition(con)); } if (columnDef.IdentityOptions != null) wColumnDef.IdentityOptions = new WIdentityOptions { FirstTokenIndex = columnDef.IdentityOptions.FirstTokenIndex, LastTokenIndex = columnDef.IdentityOptions.LastTokenIndex, IdentitySeed = ParseScalarExpression(columnDef.IdentityOptions.IdentitySeed), IdentityIncrement = ParseScalarExpression(columnDef.IdentityOptions.IdentityIncrement), IsIdentityNotForReplication = columnDef.IdentityOptions.IsIdentityNotForReplication, }; return wColumnDef; }
/// <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; }