diff --git a/lex/dialect_sql.go b/lex/dialect_sql.go index ca73963..60e93f8 100644 --- a/lex/dialect_sql.go +++ b/lex/dialect_sql.go @@ -461,6 +461,17 @@ func lexAs(l *Lexer) StateFn { } return nil } +func lexOn(l *Lexer) StateFn { + l.SkipWhiteSpaces() + keyWord := strings.ToLower(l.PeekWord()) + switch keyWord { + case "on": + l.ConsumeWord(keyWord) + l.Emit(TokenOn) + return nil + } + return nil +} func lexNotExists(l *Lexer) StateFn { l.SkipWhiteSpaces() keyWord := strings.ToLower(l.PeekWord()) @@ -534,6 +545,9 @@ func LexDrop(l *Lexer) StateFn { case "table": l.ConsumeWord(keyWord) l.Emit(TokenTable) + case "index": + l.ConsumeWord(keyWord) + l.Emit(TokenIndex) case "source": l.ConsumeWord(keyWord) l.Emit(TokenSource) @@ -553,6 +567,8 @@ func LexDrop(l *Lexer) StateFn { return nil } l.Push("LexIdentifier", LexIdentifier) + l.Push("LexOn", lexOn) + l.Push("LexIdentifier", LexIdentifier) return lexNotExists } diff --git a/rel/parse_sql.go b/rel/parse_sql.go index 7a19a98..770ddbd 100644 --- a/rel/parse_sql.go +++ b/rel/parse_sql.go @@ -860,28 +860,47 @@ func (m *Sqlbridge) parseDrop() (*SqlDrop, error) { req.Temp = true } - // DROP (TABLE|VIEW|SOURCE|CONTINUOUSVIEW) + // DROP (TABLE|VIEW|SOURCE|INDEX|CONTINUOUSVIEW) switch m.Cur().T { - case lex.TokenTable, lex.TokenView, lex.TokenSource, lex.TokenContinuousView, + case lex.TokenTable, lex.TokenView, lex.TokenIndex, lex.TokenSource, lex.TokenContinuousView, lex.TokenSchema, lex.TokenDatabase: req.Tok = m.Next() case lex.TokenIdentity: // triggers, indexes req.Tok = m.Next() default: - return nil, m.ErrMsg("Expected view, database,schema, table, source, continuousview for DROP got") + return nil, m.ErrMsg("Expected view, database, index, schema, table, source, continuousview for DROP got") + } + + if m.Cur().T == lex.TokenIf { + m.Next() // Consume IF + if m.Next().T != lex.TokenExists { + return nil, m.ErrMsg("Expected CREATE {TABLE|INDEX|SCHEMA|DATABASE} IF EXISTS ") + } + req.IfExists = true } switch m.Cur().T { case lex.TokenTable, lex.TokenIdentity: req.Identity = m.Next().V default: - return nil, m.ErrMsg("Expected identity after DROP (TABLE|VIEW|SOURCE|SCHEMA|DATABASE) ") + return nil, m.ErrMsg("Expected identity after DROP (TABLE|INDEX|VIEW|SOURCE|SCHEMA|DATABASE) ") } switch req.Tok.T { case lex.TokenTable: // just table + case lex.TokenIndex: + t := m.Next() // consume ON + if t.T != lex.TokenOn { + return nil, m.ErrMsg("Expected ON") + } + t = m.Next() // consume Identity + if t.T != lex.TokenIdentity { + return nil, m.ErrMsg("Expected Identity table name") + } + req.Parent = t.V + discardComments(m) case lex.TokenSource, lex.TokenSchema: // schema case lex.TokenContinuousView, lex.TokenView: diff --git a/rel/parse_sql_test.go b/rel/parse_sql_test.go index 2eec2bf..017380a 100644 --- a/rel/parse_sql_test.go +++ b/rel/parse_sql_test.go @@ -740,13 +740,35 @@ func TestSqlDrop(t *testing.T) { t.Parallel() sql := `DROP TABLE articles;` req, err := rel.ParseSql(sql) - assert.Equal(t, nil, err) - assert.NotEqual(t, nil, req) + assert.NoError(t, err) + assert.NotNil(t, req) ds, ok := req.(*rel.SqlDrop) assert.True(t, ok, "wanted SqlDrop got %T", req) assert.Equal(t, lex.TokenDrop, ds.Keyword(), "Has keyword DROP") assert.Equal(t, "TABLE", ds.Tok.V, "Wanted TABLE: got %q", ds.Tok.V) assert.Equal(t, "articles", ds.Identity, "has articles: %v", ds.Identity) + + sql = `DROP INDEX idx_user_email ON users;` + req, err = rel.ParseSql(sql) + assert.NoError(t, err) + assert.NotNil(t, req) + ds, ok = req.(*rel.SqlDrop) + assert.True(t, ok, "wanted SqlDrop got %T", req) + assert.Equal(t, lex.TokenDrop, ds.Keyword(), "Has keyword DROP") + assert.Equal(t, "INDEX", ds.Tok.V, "Wanted INDEX: got %q", ds.Tok.V) + assert.Equal(t, "idx_user_email", ds.Identity, "has idx_user_email: %v", ds.Identity) + + sql = `DROP INDEX IF EXISTS idx_user_name ON users;` + req, err = rel.ParseSql(sql) + assert.NoError(t, err) + assert.NotNil(t, req) + ds, ok = req.(*rel.SqlDrop) + assert.True(t, ok, "wanted SqlDrop got %T", req) + assert.Equal(t, lex.TokenDrop, ds.Keyword(), "Has keyword DROP") + assert.Equal(t, "INDEX", ds.Tok.V, "Wanted INDEX: got %q", ds.Tok.V) + assert.Equal(t, "idx_user_name", ds.Identity, "has idx_user_name: %v", ds.Identity) + assert.Equal(t, "users", ds.Parent, "has table users: %v", ds.Parent) + assert.True(t, ds.IfExists, "Expected IfExists to be true") } func TestWithNameValue(t *testing.T) { diff --git a/rel/sql.go b/rel/sql.go index 9fab16e..de79825 100644 --- a/rel/sql.go +++ b/rel/sql.go @@ -221,6 +221,8 @@ type ( Temp bool // Temp? Tok lex.Token // DROP [TEMP] [TABLE,VIEW,CONTINUOUSVIEW,TRIGGER] etc With u.JsonHelper + IfExists bool + Parent string } // SqlAlter SQL ALTER statement SqlAlter struct {