diff --git a/pkg/models/token_type.go b/pkg/models/token_type.go index 40abdfaf..b909af43 100644 --- a/pkg/models/token_type.go +++ b/pkg/models/token_type.go @@ -277,6 +277,7 @@ const ( TokenTypeRefresh TokenType = 375 TokenTypeTies TokenType = 376 // TIES keyword for WITH TIES in FETCH clause TokenTypePercent TokenType = 377 // PERCENT keyword for FETCH ... PERCENT ROWS + TokenTypeTruncate TokenType = 378 // TRUNCATE keyword for TRUNCATE TABLE statement // Grouping Set Keywords (390-399) TokenTypeGroupingSets TokenType = 390 @@ -545,6 +546,7 @@ var tokenStringMap = map[TokenType]string{ TokenTypeRefresh: "REFRESH", TokenTypeTies: "TIES", TokenTypePercent: "PERCENT", + TokenTypeTruncate: "TRUNCATE", // Grouping Set Keywords TokenTypeGroupingSets: "GROUPING_SETS", @@ -666,7 +668,7 @@ func (t TokenType) IsDMLKeyword() bool { // IsDDLKeyword returns true if the token type is a DDL keyword func (t TokenType) IsDDLKeyword() bool { switch t { - case TokenTypeCreate, TokenTypeAlter, TokenTypeDrop, TokenTypeTable, + case TokenTypeCreate, TokenTypeAlter, TokenTypeDrop, TokenTypeTruncate, TokenTypeTable, TokenTypeIndex, TokenTypeView, TokenTypeColumn, TokenTypeDatabase, TokenTypeSchema, TokenTypeTrigger: return true diff --git a/pkg/sql/ast/ast.go b/pkg/sql/ast/ast.go index c9d58a0c..d8148680 100644 --- a/pkg/sql/ast/ast.go +++ b/pkg/sql/ast/ast.go @@ -1074,6 +1074,19 @@ func (d *DropStatement) statementNode() {} func (d DropStatement) TokenLiteral() string { return "DROP " + d.ObjectType } func (d DropStatement) Children() []Node { return nil } +// TruncateStatement represents a TRUNCATE TABLE statement +// Syntax: TRUNCATE [TABLE] table_name [, table_name ...] [RESTART IDENTITY | CONTINUE IDENTITY] [CASCADE | RESTRICT] +type TruncateStatement struct { + Tables []string // Table names to truncate + RestartIdentity bool // RESTART IDENTITY - reset sequences + ContinueIdentity bool // CONTINUE IDENTITY - keep sequences (default) + CascadeType string // CASCADE, RESTRICT, or empty +} + +func (t *TruncateStatement) statementNode() {} +func (t TruncateStatement) TokenLiteral() string { return "TRUNCATE TABLE" } +func (t TruncateStatement) Children() []Node { return nil } + // PartitionDefinition represents a partition definition in CREATE TABLE // Syntax: PARTITION name VALUES { LESS THAN (expr) | IN (list) | FROM (expr) TO (expr) } type PartitionDefinition struct { diff --git a/pkg/sql/keywords/keywords.go b/pkg/sql/keywords/keywords.go index f66adf34..dea45982 100644 --- a/pkg/sql/keywords/keywords.go +++ b/pkg/sql/keywords/keywords.go @@ -156,6 +156,11 @@ var ADDITIONAL_KEYWORDS = []Keyword{ {Word: "OPTION", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, {Word: "CASCADED", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, {Word: "LOCAL", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, + // TRUNCATE TABLE statement (SQL:2008) + {Word: "TRUNCATE", Type: models.TokenTypeTruncate, Reserved: true, ReservedForTableAlias: true}, + {Word: "RESTART", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, + {Word: "CONTINUE", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, + {Word: "IDENTITY", Type: models.TokenTypeKeyword, Reserved: false, ReservedForTableAlias: false}, } // addKeywordsWithCategory is a helper method to add multiple keywords diff --git a/pkg/sql/parser/ddl.go b/pkg/sql/parser/ddl.go index 36017501..915dc7bf 100644 --- a/pkg/sql/parser/ddl.go +++ b/pkg/sql/parser/ddl.go @@ -804,3 +804,57 @@ func (p *Parser) parseRefreshStatement() (*ast.RefreshMaterializedViewStatement, return stmt, nil } + +// parseTruncateStatement parses TRUNCATE TABLE statement +// Syntax: TRUNCATE [TABLE] table_name [, table_name ...] [RESTART IDENTITY | CONTINUE IDENTITY] [CASCADE | RESTRICT] +func (p *Parser) parseTruncateStatement() (*ast.TruncateStatement, error) { + stmt := &ast.TruncateStatement{} + + // Optional TABLE keyword + if p.isType(models.TokenTypeTable) { + p.advance() // Consume TABLE + } + + // Parse table names (can be comma-separated) + for { + if !p.isType(models.TokenTypeIdentifier) { + return nil, p.expectedError("table name") + } + stmt.Tables = append(stmt.Tables, p.currentToken.Literal) + p.advance() + + if p.isType(models.TokenTypeComma) { + p.advance() // Consume comma + continue + } + break + } + + // Parse optional RESTART IDENTITY / CONTINUE IDENTITY + if p.isTokenMatch("RESTART") { + p.advance() // Consume RESTART + if !p.isTokenMatch("IDENTITY") { + return nil, p.expectedError("IDENTITY after RESTART") + } + p.advance() // Consume IDENTITY + stmt.RestartIdentity = true + } else if p.isTokenMatch("CONTINUE") { + p.advance() // Consume CONTINUE + if !p.isTokenMatch("IDENTITY") { + return nil, p.expectedError("IDENTITY after CONTINUE") + } + p.advance() // Consume IDENTITY + stmt.ContinueIdentity = true + } + + // Parse optional CASCADE/RESTRICT + if p.isType(models.TokenTypeCascade) { + stmt.CascadeType = "CASCADE" + p.advance() + } else if p.isType(models.TokenTypeRestrict) { + stmt.CascadeType = "RESTRICT" + p.advance() + } + + return stmt, nil +} diff --git a/pkg/sql/parser/parser.go b/pkg/sql/parser/parser.go index 123243bc..d0bd4104 100644 --- a/pkg/sql/parser/parser.go +++ b/pkg/sql/parser/parser.go @@ -222,7 +222,7 @@ func (p *Parser) Release() { } // parseStatement parses a single SQL statement -// Uses fast int-based ModelType comparisons with fallback for hot path optimization +// Uses O(1) switch dispatch on ModelType (compiles to jump table) for optimal performance func (p *Parser) parseStatement() (ast.Statement, error) { // Check context if available if p.ctx != nil { @@ -264,6 +264,9 @@ func (p *Parser) parseStatement() (ast.Statement, error) { case models.TokenTypeRefresh: p.advance() return p.parseRefreshStatement() + case models.TokenTypeTruncate: + p.advance() + return p.parseTruncateStatement() } // ModelType set but not a statement keyword - fall through to fallback } @@ -273,7 +276,6 @@ func (p *Parser) parseStatement() (ast.Statement, error) { if p.isType(models.TokenTypeWith) { return p.parseWithStatement() } - // Use matchType() for check-and-advance pattern if p.matchType(models.TokenTypeSelect) { return p.parseSelectWithSetOperations() } @@ -301,6 +303,9 @@ func (p *Parser) parseStatement() (ast.Statement, error) { if p.matchType(models.TokenTypeRefresh) { return p.parseRefreshStatement() } + if p.matchType(models.TokenTypeTruncate) { + return p.parseTruncateStatement() + } return nil, p.expectedError("statement") } @@ -387,6 +392,7 @@ var modelTypeToString = map[models.TokenType]token.Type{ models.TokenTypeCreate: "CREATE", models.TokenTypeAlter: token.ALTER, models.TokenTypeDrop: token.DROP, + models.TokenTypeTruncate: "TRUNCATE", models.TokenTypeTable: "TABLE", models.TokenTypeIndex: "INDEX", models.TokenTypeView: "VIEW", diff --git a/pkg/sql/parser/token_converter.go b/pkg/sql/parser/token_converter.go index 611fc84b..ce8bf793 100644 --- a/pkg/sql/parser/token_converter.go +++ b/pkg/sql/parser/token_converter.go @@ -524,6 +524,7 @@ func buildTypeMapping() map[models.TokenType]token.Type { models.TokenTypeCreate: "CREATE", models.TokenTypeAlter: "ALTER", models.TokenTypeDrop: "DROP", + models.TokenTypeTruncate: "TRUNCATE", models.TokenTypeTable: "TABLE", models.TokenTypeIndex: "INDEX", models.TokenTypeView: "VIEW", diff --git a/pkg/sql/parser/truncate_test.go b/pkg/sql/parser/truncate_test.go new file mode 100644 index 00000000..22c28ef4 --- /dev/null +++ b/pkg/sql/parser/truncate_test.go @@ -0,0 +1,513 @@ +package parser + +import ( + "testing" + + "github.com/ajitpratap0/GoSQLX/pkg/models" + "github.com/ajitpratap0/GoSQLX/pkg/sql/ast" + "github.com/ajitpratap0/GoSQLX/pkg/sql/token" + "github.com/ajitpratap0/GoSQLX/pkg/sql/tokenizer" +) + +// convertTokensForTruncate converts TokenWithSpan to Token for parser +func convertTokensForTruncate(tokens []models.TokenWithSpan) []token.Token { + result := make([]token.Token, 0, len(tokens)) + for _, t := range tokens { + // Determine token type + tokenType := token.Type(t.Token.Value) + if t.Token.Type == models.TokenTypeIdentifier { + tokenType = "IDENT" + } else if t.Token.Type == models.TokenTypeNumber { + tokenType = "NUMBER" + } else if t.Token.Type == models.TokenTypeString { + tokenType = "STRING" + } else if t.Token.Type == models.TokenTypeEOF { + tokenType = token.EOF + } + + result = append(result, token.Token{ + Type: tokenType, + Literal: t.Token.Value, + ModelType: t.Token.Type, + }) + } + return result +} + +func TestParser_TruncateTable_Basic(t *testing.T) { + sql := "TRUNCATE TABLE users" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + if len(result.Statements) != 1 { + t.Fatalf("Expected 1 statement, got %d", len(result.Statements)) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if len(truncateStmt.Tables) != 1 || truncateStmt.Tables[0] != "users" { + t.Errorf("Expected tables [users], got %v", truncateStmt.Tables) + } + + if truncateStmt.RestartIdentity { + t.Error("RestartIdentity should be false") + } + + if truncateStmt.ContinueIdentity { + t.Error("ContinueIdentity should be false") + } + + if truncateStmt.CascadeType != "" { + t.Errorf("CascadeType should be empty, got %q", truncateStmt.CascadeType) + } +} + +func TestParser_TruncateTable_WithoutTableKeyword(t *testing.T) { + sql := "TRUNCATE users" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + if len(result.Statements) != 1 { + t.Fatalf("Expected 1 statement, got %d", len(result.Statements)) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if len(truncateStmt.Tables) != 1 || truncateStmt.Tables[0] != "users" { + t.Errorf("Expected tables [users], got %v", truncateStmt.Tables) + } +} + +func TestParser_TruncateTable_MultipleTables(t *testing.T) { + sql := "TRUNCATE TABLE users, orders, products" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + if len(result.Statements) != 1 { + t.Fatalf("Expected 1 statement, got %d", len(result.Statements)) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + expectedTables := []string{"users", "orders", "products"} + if len(truncateStmt.Tables) != len(expectedTables) { + t.Fatalf("Expected %d tables, got %d", len(expectedTables), len(truncateStmt.Tables)) + } + + for i, table := range expectedTables { + if truncateStmt.Tables[i] != table { + t.Errorf("Expected table %q at index %d, got %q", table, i, truncateStmt.Tables[i]) + } + } +} + +func TestParser_TruncateTable_RestartIdentity(t *testing.T) { + sql := "TRUNCATE TABLE users RESTART IDENTITY" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if !truncateStmt.RestartIdentity { + t.Error("RestartIdentity should be true") + } + + if truncateStmt.ContinueIdentity { + t.Error("ContinueIdentity should be false") + } +} + +func TestParser_TruncateTable_ContinueIdentity(t *testing.T) { + sql := "TRUNCATE TABLE users CONTINUE IDENTITY" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if truncateStmt.RestartIdentity { + t.Error("RestartIdentity should be false") + } + + if !truncateStmt.ContinueIdentity { + t.Error("ContinueIdentity should be true") + } +} + +func TestParser_TruncateTable_Cascade(t *testing.T) { + sql := "TRUNCATE TABLE users CASCADE" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if truncateStmt.CascadeType != "CASCADE" { + t.Errorf("Expected CascadeType 'CASCADE', got %q", truncateStmt.CascadeType) + } +} + +func TestParser_TruncateTable_Restrict(t *testing.T) { + sql := "TRUNCATE TABLE users RESTRICT" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if truncateStmt.CascadeType != "RESTRICT" { + t.Errorf("Expected CascadeType 'RESTRICT', got %q", truncateStmt.CascadeType) + } +} + +func TestParser_TruncateTable_FullSyntax(t *testing.T) { + sql := "TRUNCATE TABLE users RESTART IDENTITY CASCADE" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if len(truncateStmt.Tables) != 1 || truncateStmt.Tables[0] != "users" { + t.Errorf("Expected tables [users], got %v", truncateStmt.Tables) + } + + if !truncateStmt.RestartIdentity { + t.Error("RestartIdentity should be true") + } + + if truncateStmt.ContinueIdentity { + t.Error("ContinueIdentity should be false") + } + + if truncateStmt.CascadeType != "CASCADE" { + t.Errorf("Expected CascadeType 'CASCADE', got %q", truncateStmt.CascadeType) + } +} + +func TestParser_TruncateTable_MultipleTablesWithRestrict(t *testing.T) { + sql := "TRUNCATE TABLE users, orders CONTINUE IDENTITY RESTRICT" + + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + expectedTables := []string{"users", "orders"} + if len(truncateStmt.Tables) != len(expectedTables) { + t.Fatalf("Expected %d tables, got %d", len(expectedTables), len(truncateStmt.Tables)) + } + + for i, table := range expectedTables { + if truncateStmt.Tables[i] != table { + t.Errorf("Expected table %q at index %d, got %q", table, i, truncateStmt.Tables[i]) + } + } + + if truncateStmt.RestartIdentity { + t.Error("RestartIdentity should be false") + } + + if !truncateStmt.ContinueIdentity { + t.Error("ContinueIdentity should be true") + } + + if truncateStmt.CascadeType != "RESTRICT" { + t.Errorf("Expected CascadeType 'RESTRICT', got %q", truncateStmt.CascadeType) + } +} + +func TestParser_TruncateStatement_TokenLiteral(t *testing.T) { + stmt := &ast.TruncateStatement{ + Tables: []string{"users"}, + } + + if stmt.TokenLiteral() != "TRUNCATE TABLE" { + t.Errorf("Expected TokenLiteral 'TRUNCATE TABLE', got %q", stmt.TokenLiteral()) + } +} + +func TestParser_TruncateStatement_Children(t *testing.T) { + stmt := &ast.TruncateStatement{ + Tables: []string{"users", "orders"}, + } + + children := stmt.Children() + if children != nil { + t.Errorf("Expected Children() to return nil, got %v", children) + } +} + +// TestParser_TruncateTable_TableDriven tests various TRUNCATE TABLE scenarios +func TestParser_TruncateTable_TableDriven(t *testing.T) { + tests := []struct { + name string + sql string + expectedTables []string + restartIdentity bool + continueIdentity bool + cascadeType string + shouldErr bool + }{ + { + name: "basic truncate", + sql: "TRUNCATE TABLE users", + expectedTables: []string{"users"}, + }, + { + name: "without TABLE keyword", + sql: "TRUNCATE users", + expectedTables: []string{"users"}, + }, + { + name: "multiple tables", + sql: "TRUNCATE TABLE a, b, c", + expectedTables: []string{"a", "b", "c"}, + }, + { + name: "restart identity", + sql: "TRUNCATE TABLE t1 RESTART IDENTITY", + expectedTables: []string{"t1"}, + restartIdentity: true, + }, + { + name: "continue identity", + sql: "TRUNCATE TABLE t1 CONTINUE IDENTITY", + expectedTables: []string{"t1"}, + continueIdentity: true, + }, + { + name: "cascade", + sql: "TRUNCATE TABLE t1 CASCADE", + expectedTables: []string{"t1"}, + cascadeType: "CASCADE", + }, + { + name: "restrict", + sql: "TRUNCATE TABLE t1 RESTRICT", + expectedTables: []string{"t1"}, + cascadeType: "RESTRICT", + }, + { + name: "full syntax with restart", + sql: "TRUNCATE TABLE users RESTART IDENTITY CASCADE", + expectedTables: []string{"users"}, + restartIdentity: true, + cascadeType: "CASCADE", + }, + { + name: "full syntax with continue", + sql: "TRUNCATE TABLE users CONTINUE IDENTITY RESTRICT", + expectedTables: []string{"users"}, + continueIdentity: true, + cascadeType: "RESTRICT", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tkz := tokenizer.GetTokenizer() + defer tokenizer.PutTokenizer(tkz) + + tokens, err := tkz.Tokenize([]byte(tt.sql)) + if err != nil { + t.Fatalf("Failed to tokenize: %v", err) + } + + convertedTokens := convertTokensForTruncate(tokens) + + parser := &Parser{} + result, err := parser.Parse(convertedTokens) + + if tt.shouldErr { + if err == nil { + t.Fatal("Expected error, got none") + } + return + } + + if err != nil { + t.Fatalf("Failed to parse: %v", err) + } + + if len(result.Statements) != 1 { + t.Fatalf("Expected 1 statement, got %d", len(result.Statements)) + } + + truncateStmt, ok := result.Statements[0].(*ast.TruncateStatement) + if !ok { + t.Fatalf("Expected TruncateStatement, got %T", result.Statements[0]) + } + + if len(truncateStmt.Tables) != len(tt.expectedTables) { + t.Fatalf("Expected %d tables, got %d", len(tt.expectedTables), len(truncateStmt.Tables)) + } + + for i, table := range tt.expectedTables { + if truncateStmt.Tables[i] != table { + t.Errorf("Expected table %q at index %d, got %q", table, i, truncateStmt.Tables[i]) + } + } + + if truncateStmt.RestartIdentity != tt.restartIdentity { + t.Errorf("Expected RestartIdentity=%v, got %v", tt.restartIdentity, truncateStmt.RestartIdentity) + } + + if truncateStmt.ContinueIdentity != tt.continueIdentity { + t.Errorf("Expected ContinueIdentity=%v, got %v", tt.continueIdentity, truncateStmt.ContinueIdentity) + } + + if truncateStmt.CascadeType != tt.cascadeType { + t.Errorf("Expected CascadeType=%q, got %q", tt.cascadeType, truncateStmt.CascadeType) + } + }) + } +} diff --git a/pkg/sql/tokenizer/tokenizer.go b/pkg/sql/tokenizer/tokenizer.go index 8054d7b5..3e09979c 100644 --- a/pkg/sql/tokenizer/tokenizer.go +++ b/pkg/sql/tokenizer/tokenizer.go @@ -100,6 +100,7 @@ var keywordTokenTypes = map[string]models.TokenType{ "CREATE": models.TokenTypeCreate, "DROP": models.TokenTypeDrop, "ALTER": models.TokenTypeAlter, + "TRUNCATE": models.TokenTypeTruncate, "TABLE": models.TokenTypeTable, "INDEX": models.TokenTypeIndex, "VIEW": models.TokenTypeView,