Go CLI: Fix alter role (#16226)

### What problem does this PR solve?

As title.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-06-22 17:33:47 +08:00
committed by GitHub
parent 329e09f16a
commit 05e758e4fe
10 changed files with 453 additions and 396 deletions

View File

@@ -50,7 +50,7 @@ func (p *Parser) parseAdminLoginUser() (*Command, error) {
p.nextToken()
}
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -61,7 +61,7 @@ func (p *Parser) parseAdminLoginUser() (*Command, error) {
func (p *Parser) parseAdminLogout() (*Command, error) {
cmd := NewCommand("logout")
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -71,7 +71,7 @@ func (p *Parser) parseAdminLogout() (*Command, error) {
func (p *Parser) parseAdminPingServer() (*Command, error) {
cmd := NewCommand("ping_server")
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -208,7 +208,7 @@ func (p *Parser) parseAdminListFiles() (*Command, error) {
cmd.Params["dataset_name"] = datasetName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -232,7 +232,7 @@ func (p *Parser) parseAdminListIngestionTasks() (*Command, error) {
cmd := NewCommand("list_admin_ingestion_tasks")
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -321,16 +321,16 @@ func (p *Parser) parseAdminShowCommand() (*Command, error) {
func (p *Parser) parseAdminShowService() (*Command, error) {
p.nextToken() // consume SERVICE
serviceNum, err := p.parseNumber()
serviceIndex, err := p.parseNumber()
if err != nil {
return nil, err
}
cmd := NewCommand("admin_show_service")
cmd.Params["number"] = serviceNum
cmd.Params["service_index"] = serviceIndex
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -538,12 +538,19 @@ func (p *Parser) parseAdminShowRole() (*Command, error) {
if err != nil {
return nil, err
}
cmd := NewCommand("admin_show_role")
cmd.Params["role_name"] = roleName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
var cmd *Command
if p.curToken.Type == TokenPermission {
p.nextToken()
cmd = NewCommand("admin_show_role_permission")
cmd.Params["role_name"] = roleName
} else {
cmd = NewCommand("admin_show_role")
cmd.Params["role_name"] = roleName
}
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -574,7 +581,7 @@ func (p *Parser) parseAdminShowVariable() (*Command, error) {
cmd.Params["var_name"] = varName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -694,7 +701,7 @@ func (p *Parser) parseCommonShowPoolModel() (*Command, error) {
cmd := NewCommand("show_pool_provider")
cmd.Params["provider_name"] = providerName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -718,7 +725,7 @@ func (p *Parser) parseCommonShowPoolModel() (*Command, error) {
cmd := NewCommand("show_pool_model")
cmd.Params["provider_name"] = providerName
cmd.Params["model_name"] = modelName
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -752,6 +759,7 @@ func (p *Parser) parseAdminCheckLicense() (*Command, error) {
return cmd, nil
}
// STOP INGESTION TASKS 'task_id1 task_id2';
func (p *Parser) parseAdminStopIngestionTasks() (*Command, error) {
p.nextToken() // consume STOP
@@ -781,7 +789,7 @@ func (p *Parser) parseAdminStopIngestionTasks() (*Command, error) {
return nil, fmt.Errorf("expected USER or INGESTION")
}
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -790,7 +798,7 @@ func (p *Parser) parseAdminStopIngestionTasks() (*Command, error) {
}
func (p *Parser) parseAdminRemoveIngestionTasks() (*Command, error) {
p.nextToken() // consume Ingestion
p.nextToken() // consume INGESTION
if p.curToken.Type != TokenTasks {
return nil, fmt.Errorf("expected TASKS")
@@ -808,7 +816,7 @@ func (p *Parser) parseAdminRemoveIngestionTasks() (*Command, error) {
cmd := NewCommand("admin_remove_ingestion_tasks")
cmd.Params["tasks"] = tasks
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -858,7 +866,7 @@ func (p *Parser) parseAdminCreateUser() (*Command, error) {
return nil, fmt.Errorf("expected password or KEY after USER, got %s", p.curToken.Value)
}
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -903,7 +911,7 @@ func (p *Parser) parseAdminCreateRole() (*Command, error) {
p.nextToken()
}
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -982,7 +990,7 @@ func (p *Parser) parseAdminDropRole() (*Command, error) {
cmd.Params["role_name"] = roleName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1005,7 +1013,7 @@ func (p *Parser) parseAdminDropModelProvider() (*Command, error) {
cmd.Params["provider_name"] = providerName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1023,7 +1031,7 @@ func (p *Parser) parseAdminDropDataset() (*Command, error) {
cmd.Params["dataset_name"] = datasetName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1041,7 +1049,7 @@ func (p *Parser) parseAdminDropChat() (*Command, error) {
cmd.Params["chat_name"] = chatName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1089,7 +1097,7 @@ func (p *Parser) parseAdminAlterUser() (*Command, error) {
cmd.Params["password"] = password
p.nextToken()
// Semicolon is optional for SHOW TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1121,7 +1129,34 @@ func (p *Parser) parseAdminAlterUser() (*Command, error) {
cmd.Params["role_name"] = roleName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminActivateUser() (*Command, error) {
p.nextToken() // consume ACTIVE
userName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
p.nextToken()
// Accept 'on' or 'off' as identifier
status := p.curToken.Value
if status != "on" && status != "off" {
return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value)
}
p.nextToken()
cmd := NewCommand("admin_activate_user")
cmd.Params["user_name"] = userName
cmd.Params["activate_status"] = status
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1130,16 +1165,18 @@ func (p *Parser) parseAdminAlterUser() (*Command, error) {
func (p *Parser) parseAdminAlterRole() (*Command, error) {
p.nextToken() // consume ROLE
roleName, err := p.parseIdentifier()
roleName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
p.nextToken()
if p.curToken.Type != TokenSet {
return nil, fmt.Errorf("expected SET")
}
p.nextToken()
if p.curToken.Type != TokenDescription {
return nil, fmt.Errorf("expected DESCRIPTION")
}
@@ -1149,13 +1186,13 @@ func (p *Parser) parseAdminAlterRole() (*Command, error) {
if err != nil {
return nil, err
}
p.nextToken()
cmd := NewCommand("alter_role")
cmd := NewCommand("admin_alter_role")
cmd.Params["role_name"] = roleName
cmd.Params["description"] = description
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1163,33 +1200,8 @@ func (p *Parser) parseAdminAlterRole() (*Command, error) {
}
// endregion ALTER commands
func (p *Parser) parseAdminActivateUser() (*Command, error) {
p.nextToken() // consume ACTIVE
userName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
p.nextToken()
// Accept 'on' or 'off' as identifier
status := p.curToken.Value
if status != "on" && status != "off" {
return nil, fmt.Errorf("expected 'on' or 'off', got %s", p.curToken.Value)
}
cmd := NewCommand("admin_activate_user")
cmd.Params["user_name"] = userName
cmd.Params["activate_status"] = status
p.nextToken()
// Semicolon is optional for UNSET TOKEN
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminGrantCommand() (*Command, error) {
func (p *Parser) parseAdminGrantCommands() (*Command, error) {
p.nextToken() // consume GRANT
if p.curToken.Type == TokenAdmin {
@@ -1206,11 +1218,11 @@ func (p *Parser) parseAdminGrantAdmin() (*Command, error) {
return nil, err
}
cmd := NewCommand("grant_admin")
cmd := NewCommand("admin_grant_user_admin")
cmd.Params["user_name"] = userName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1218,50 +1230,56 @@ func (p *Parser) parseAdminGrantAdmin() (*Command, error) {
}
func (p *Parser) parseAdminGrantPermission() (*Command, error) {
actions, err := p.parseIdentifierList()
actionListStr, err := p.parseQuotedString()
if err != nil {
return nil, err
}
actions := strings.Split(actionListStr, ",")
p.nextToken()
for idx, _ := range actions {
actions[idx] = strings.TrimSpace(actions[idx])
}
if p.curToken.Type != TokenOn {
return nil, fmt.Errorf("expected ON")
}
p.nextToken()
resource, err := p.parseIdentifier()
resource, err := p.parseQuotedString()
if err != nil {
return nil, err
}
p.nextToken()
if p.curToken.Type != TokenTo {
return nil, fmt.Errorf("expected TO")
}
p.nextToken()
if p.curToken.Type != TokenRole {
return nil, fmt.Errorf("expected ROLE")
}
p.nextToken()
roleName, err := p.parseIdentifier()
roleName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
cmd := NewCommand("grant_permission")
cmd := NewCommand("admin_grant_role_permission")
cmd.Params["actions"] = actions
cmd.Params["resource"] = resource
cmd.Params["role_name"] = roleName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminRevokeCommand() (*Command, error) {
func (p *Parser) parseAdminRevokeCommands() (*Command, error) {
p.nextToken() // consume REVOKE
if p.curToken.Type == TokenAdmin {
@@ -1278,11 +1296,11 @@ func (p *Parser) parseAdminRevokeAdmin() (*Command, error) {
return nil, err
}
cmd := NewCommand("revoke_admin")
cmd := NewCommand("admin_revoke_user_admin")
cmd.Params["user_name"] = userName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1290,17 +1308,22 @@ func (p *Parser) parseAdminRevokeAdmin() (*Command, error) {
}
func (p *Parser) parseAdminRevokePermission() (*Command, error) {
actions, err := p.parseIdentifierList()
actionListStr, err := p.parseQuotedString()
if err != nil {
return nil, err
}
actions := strings.Split(actionListStr, ",")
p.nextToken()
for idx, _ := range actions {
actions[idx] = strings.TrimSpace(actions[idx])
}
if p.curToken.Type != TokenOn {
return nil, fmt.Errorf("expected ON")
}
p.nextToken()
resource, err := p.parseIdentifier()
resource, err := p.parseQuotedString()
if err != nil {
return nil, err
}
@@ -1315,18 +1338,18 @@ func (p *Parser) parseAdminRevokePermission() (*Command, error) {
}
p.nextToken()
roleName, err := p.parseIdentifier()
roleName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
cmd := NewCommand("revoke_permission")
cmd := NewCommand("admin_revoke_role_permission")
cmd.Params["actions"] = actions
cmd.Params["resource"] = resource
cmd.Params["role_name"] = roleName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1366,8 +1389,6 @@ func (p *Parser) parseAdminSetCommand() (*Command, error) {
return p.parseAdminSetVariable()
case TokenDefault:
return p.parseAdminSetDefault()
case TokenToken:
return p.parseAdminSetToken()
default:
return nil, fmt.Errorf("unknown SET target: %s", p.curToken.Value)
}
@@ -1413,7 +1434,8 @@ func (p *Parser) parseAdminSetLicense() (*Command, error) {
func (p *Parser) parseAdminSetVariable() (*Command, error) {
p.nextToken() // consume VAR
varName, err := p.parseIdentifier()
varName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
@@ -1429,7 +1451,7 @@ func (p *Parser) parseAdminSetVariable() (*Command, error) {
cmd.Params["var_value"] = varValue
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1477,7 +1499,7 @@ func (p *Parser) parseAdminSetDefault() (*Command, error) {
}
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1496,7 +1518,7 @@ func (p *Parser) parseAdminSetToken() (*Command, error) {
cmd.Params["token"] = tokenValue
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1534,37 +1556,6 @@ func (p *Parser) parseAdminResetCommand() (*Command, error) {
cmd := NewCommand("reset_default_model")
cmd.Params["model_type"] = modelType
p.nextToken()
// Semicolon is optional for UNSET TOKEN
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminGenerateCommand() (*Command, error) {
p.nextToken() // consume GENERATE
if p.curToken.Type != TokenToken {
return nil, fmt.Errorf("expected TOKEN")
}
p.nextToken()
if p.curToken.Type != TokenFor {
return nil, fmt.Errorf("expected FOR")
}
p.nextToken()
if p.curToken.Type != TokenUser {
return nil, fmt.Errorf("expected USER")
}
p.nextToken()
userName, err := p.parseQuotedString()
if err != nil {
return nil, err
}
cmd := NewCommand("generate_token")
cmd.Params["user_name"] = userName
p.nextToken()
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
@@ -1600,7 +1591,7 @@ func (p *Parser) parseAdminImportCommand() (*Command, error) {
cmd.Params["dataset_name"] = datasetName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1634,7 +1625,7 @@ func (p *Parser) parseAdminRetrieveCommand() (*Command, error) {
cmd.Params["datasets"] = datasets
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1673,7 +1664,7 @@ func (p *Parser) parseAdminParseDataset() (*Command, error) {
cmd.Params["method"] = method
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1706,7 +1697,7 @@ func (p *Parser) parseAdminParseDocs() (*Command, error) {
cmd.Params["dataset_name"] = datasetName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1770,62 +1761,63 @@ func (p *Parser) parseAdminUserStatement() (*Command, error) {
}
}
func (p *Parser) parseAdminStartupCommand() (*Command, error) {
p.nextToken() // consume STARTUP
func (p *Parser) parseAdminStartService() (*Command, error) {
p.nextToken() // consume START
if p.curToken.Type != TokenService {
return nil, fmt.Errorf("expected SERVICE")
}
p.nextToken()
serviceNum, err := p.parseNumber()
serviceIndex, err := p.parseNumber()
if err != nil {
return nil, err
}
cmd := NewCommand("startup_service")
cmd.Params["number"] = serviceNum
cmd := NewCommand("admin_start_service")
cmd.Params["service_index"] = serviceIndex
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminShutdownCommand() (*Command, error) {
func (p *Parser) parseAdminShutdownCommands() (*Command, error) {
p.nextToken() // consume SHUTDOWN
switch p.curToken.Type {
case TokenService:
return p.parseAdminShutdownServiceCommand()
return p.parseAdminShutdownService()
case TokenIngestor:
return p.parseAdminShutdownIngestorCommand()
return p.parseAdminShutdownIngestor()
default:
return nil, fmt.Errorf("expected SERVICE or INGESTOR")
}
}
func (p *Parser) parseAdminShutdownServiceCommand() (*Command, error) {
func (p *Parser) parseAdminShutdownService() (*Command, error) {
p.nextToken() // consume SERVICE
serviceNum, err := p.parseNumber()
serviceIndex, err := p.parseNumber()
if err != nil {
return nil, err
}
cmd := NewCommand("shutdown_service")
cmd.Params["number"] = serviceNum
cmd := NewCommand("admin_shutdown_service")
cmd.Params["service_index"] = serviceIndex
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminShutdownIngestorCommand() (*Command, error) {
func (p *Parser) parseAdminShutdownIngestor() (*Command, error) {
p.nextToken() // consume INGESTOR
ingestorName, err := p.parseQuotedString()
@@ -1837,30 +1829,30 @@ func (p *Parser) parseAdminShutdownIngestorCommand() (*Command, error) {
cmd.Params["ingestor_name"] = ingestorName
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
func (p *Parser) parseAdminRestartCommand() (*Command, error) {
func (p *Parser) parseAdminRestart() (*Command, error) {
p.nextToken() // consume RESTART
if p.curToken.Type != TokenService {
return nil, fmt.Errorf("expected SERVICE")
}
p.nextToken()
serviceNum, err := p.parseNumber()
serviceIndex, err := p.parseNumber()
if err != nil {
return nil, err
}
cmd := NewCommand("restart_service")
cmd.Params["number"] = serviceNum
cmd := NewCommand("admin_restart_service")
cmd.Params["service_index"] = serviceIndex
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1930,7 +1922,7 @@ func (p *Parser) parseStartIngestion() (*Command, error) {
cmd.Params["uri"] = uri
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1954,7 +1946,7 @@ func (p *Parser) parseStopIngestion() (*Command, error) {
cmd.Params["task_id"] = taskID
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1973,7 +1965,7 @@ func (p *Parser) parseAdminIngestCommand() (*Command, error) {
cmd.Params["uri"] = uri
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -1987,7 +1979,7 @@ func (p *Parser) parseAdminUnsetCommand() (*Command, error) {
}
p.nextToken()
// Semicolon is optional for UNSET TOKEN
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
@@ -2059,20 +2051,12 @@ func (p *Parser) parseMessageQueueCommand() (*Command, error) {
return cmd, nil
}
func (p *Parser) parseAdminRemoveCommand() (*Command, error) {
p.nextToken() // consume MESSAGE_QUEUE
// REMOVE INGESTION TASK 'task_id';
// REMOVE USER 'user@example.com' INGESTION TASKS 'created';
func (p *Parser) parseAdminRemoveCommands() (*Command, error) {
p.nextToken() // consume REMOVE
var cmd *Command
switch p.curToken.Type {
case TokenService:
p.nextToken() // consume SERVICE
serviceNum, err := p.parseNumber()
if err != nil {
return nil, fmt.Errorf("expected service number after SERVICE")
}
p.nextToken() // consume service number
cmd = NewCommand("admin_remove_service_command")
cmd.Params["service_number"] = serviceNum
case TokenIngestion:
return p.parseAdminRemoveIngestionTasks()
case TokenUser:
@@ -2080,11 +2064,6 @@ func (p *Parser) parseAdminRemoveCommand() (*Command, error) {
default:
return nil, fmt.Errorf("expected SERVICE")
}
// Semicolon is optional
if p.curToken.Type == TokenSemicolon {
p.nextToken()
}
return cmd, nil
}
// SHOW USERS SUMMARY;