diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLGrantStatement.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLGrantStatement.java index b684986651..0b78f192da 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLGrantStatement.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLGrantStatement.java @@ -25,10 +25,10 @@ import com.alibaba.druid.sql.visitor.SQLASTVisitor; public class SQLGrantStatement extends SQLStatementImpl { - private final List privileges = new ArrayList(); + protected final List privileges = new ArrayList(); - private SQLObject on; - private SQLExpr to; + protected SQLObject on; + protected SQLExpr to; public SQLGrantStatement(){ @@ -39,15 +39,15 @@ public class SQLGrantStatement extends SQLStatementImpl { } // mysql - private SQLObjectType objectType; - private SQLExpr maxQueriesPerHour; - private SQLExpr maxUpdatesPerHour; - private SQLExpr maxConnectionsPerHour; - private SQLExpr maxUserConnections; + protected SQLObjectType objectType; + private SQLExpr maxQueriesPerHour; + private SQLExpr maxUpdatesPerHour; + private SQLExpr maxConnectionsPerHour; + private SQLExpr maxUserConnections; - private boolean adminOption; + private boolean adminOption; - private SQLExpr identifiedBy; + private SQLExpr identifiedBy; @Override protected void accept0(SQLASTVisitor visitor) { diff --git a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLObjectType.java b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLObjectType.java index 19a2de1e9b..873f2f6b03 100644 --- a/src/main/java/com/alibaba/druid/sql/ast/statement/SQLObjectType.java +++ b/src/main/java/com/alibaba/druid/sql/ast/statement/SQLObjectType.java @@ -16,5 +16,6 @@ package com.alibaba.druid.sql.ast.statement; public enum SQLObjectType { - TABLE, FUNCTION, PROCEDURE, USER, DATABASE -} \ No newline at end of file + TABLE, FUNCTION, PROCEDURE, USER, DATABASE, // + ROLE, PROJECT, PACKAGE, RESOURCE, INSTANCE, JOB, VOLUME, OfflineModel, XFLOW // for odps +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/ast/OdpsGrantStmt.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/ast/OdpsGrantStmt.java new file mode 100644 index 0000000000..fd4d6e2895 --- /dev/null +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/ast/OdpsGrantStmt.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2101 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.sql.dialect.odps.ast; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLName; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.statement.SQLGrantStatement; +import com.alibaba.druid.sql.ast.statement.SQLObjectType; +import com.alibaba.druid.sql.dialect.odps.visitor.OdpsASTVisitor; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; +import com.alibaba.druid.util.JdbcConstants; + +public class OdpsGrantStmt extends SQLGrantStatement { + + private SQLObjectType subjectType; + + private boolean isSuper = false; + + private boolean isLabel = false; + private SQLExpr label; + private List columns = new ArrayList(); ; + private SQLExpr expire; + + public OdpsGrantStmt(){ + super(JdbcConstants.ODPS); + } + + @Override + protected void accept0(SQLASTVisitor visitor) { + accept0((OdpsASTVisitor) visitor); + } + + protected void accept0(OdpsASTVisitor visitor) { + if (visitor.visit(this)) { + acceptChild(visitor, on); + acceptChild(visitor, to); + } + visitor.endVisit(this); + } + + public SQLObject getOn() { + return on; + } + + public void setOn(SQLExpr on) { + if (on != null) { + on.setParent(this); + } + this.on = on; + } + + public SQLExpr getTo() { + return to; + } + + public void setTo(SQLExpr to) { + this.to = to; + } + + public List getPrivileges() { + return privileges; + } + + public SQLObjectType getSubjectType() { + return subjectType; + } + + public void setSubjectType(SQLObjectType subjectType) { + this.subjectType = subjectType; + } + + public boolean isSuper() { + return isSuper; + } + + public void setSuper(boolean isSuper) { + this.isSuper = isSuper; + } + + public boolean isLabel() { + return isLabel; + } + + public void setLabel(boolean isLabel) { + this.isLabel = isLabel; + } + + public SQLExpr getLabel() { + return label; + } + + public void setLabel(SQLExpr label) { + this.label = label; + } + + public List getColumns() { + return columns; + } + + public void setColumnList(List columns) { + this.columns = columns; + } + + public SQLExpr getExpire() { + return expire; + } + + public void setExpire(SQLExpr expire) { + this.expire = expire; + } + +} diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsStatementParser.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsStatementParser.java index c276fa846b..6fff3b0208 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsStatementParser.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/parser/OdpsStatementParser.java @@ -19,10 +19,13 @@ import java.util.List; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLName; +import com.alibaba.druid.sql.ast.SQLObject; import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.statement.SQLAssignItem; import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLObjectType; import com.alibaba.druid.sql.ast.statement.SQLSelect; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLSetStatement; @@ -30,6 +33,7 @@ import com.alibaba.druid.sql.ast.statement.SQLShowTablesStatement; import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddStatisticStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAnalyzeTableStatement; +import com.alibaba.druid.sql.dialect.odps.ast.OdpsGrantStmt; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsert; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsertStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsListStmt; @@ -404,4 +408,166 @@ public class OdpsStatementParser extends SQLStatementParser { } } + public OdpsGrantStmt parseGrant() { + accept(Token.GRANT); + OdpsGrantStmt stmt = new OdpsGrantStmt(); + + if (identifierEquals("LABEL")) { + stmt.setLabel(true); + lexer.nextToken(); + stmt.setLabel(this.exprParser.expr()); + } else { + if (identifierEquals("SUPER")) { + stmt.setSuper(true); + lexer.nextToken(); + } + + parsePrivileages(stmt.getPrivileges(), stmt); + } + + if (lexer.token() == Token.ON) { + lexer.nextToken(); + + if (identifierEquals("PROJECT")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.PROJECT); + } else if (identifierEquals("PACKAGE")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.PACKAGE); + } else if (lexer.token() == Token.FUNCTION) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.FUNCTION); + } else if (lexer.token() == Token.TABLE) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.TABLE); + if (lexer.token() == Token.LPAREN) { + lexer.nextToken(); + this.exprParser.names(stmt.getColumns(), stmt); + accept(Token.RPAREN); + } + } else if (identifierEquals("RESOURCE")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.RESOURCE); + } else if (identifierEquals("INSTANCE")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.INSTANCE); + } else if (identifierEquals("JOB")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.JOB); + } else if (identifierEquals("VOLUME")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.VOLUME); + } else if (identifierEquals("OfflineModel")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.OfflineModel); + } else if (identifierEquals("XFLOW")) { + lexer.nextToken(); + stmt.setObjectType(SQLObjectType.XFLOW); + } + + stmt.setOn(this.exprParser.expr()); + } + + if (lexer.token() == Token.TO) { + lexer.nextToken(); + if (lexer.token() == Token.USER) { + lexer.nextToken(); + stmt.setSubjectType(SQLObjectType.USER); + } else if (identifierEquals("ROLE")) { + lexer.nextToken(); + stmt.setSubjectType(SQLObjectType.ROLE); + } + stmt.setTo(this.exprParser.expr()); + } + + if (lexer.token() == Token.WITH) { + lexer.nextToken(); + acceptIdentifier("EXP"); + stmt.setExpire(this.exprParser.expr()); + } + + return stmt; + } + + protected void parsePrivileages(List privileges, SQLObject parent) { + for (;;) { + String privilege = null; + if (lexer.token() == Token.ALL) { + lexer.nextToken(); + privilege = "ALL"; + } else if (lexer.token() == Token.SELECT) { + privilege = "SELECT"; + lexer.nextToken(); + } else if (lexer.token() == Token.UPDATE) { + privilege = "UPDATE"; + lexer.nextToken(); + } else if (lexer.token() == Token.DELETE) { + privilege = "DELETE"; + lexer.nextToken(); + } else if (lexer.token() == Token.INSERT) { + privilege = "INSERT"; + lexer.nextToken(); + } else if (lexer.token() == Token.DROP) { + lexer.nextToken(); + privilege = "DROP"; + } else if (lexer.token() == Token.ALTER) { + lexer.nextToken(); + privilege = "ALTER"; + } else if (identifierEquals("DESCRIBE")) { + privilege = "DESCRIBE"; + lexer.nextToken(); + } else if (identifierEquals("READ")) { + privilege = "READ"; + lexer.nextToken(); + } else if (identifierEquals("WRITE")) { + privilege = "WRITE"; + lexer.nextToken(); + } else if (identifierEquals("EXECUTE")) { + lexer.nextToken(); + privilege = "EXECUTE"; + } else if (identifierEquals("LIST")) { + lexer.nextToken(); + privilege = "LIST"; + } else if (identifierEquals("CreateTable")) { + lexer.nextToken(); + privilege = "CreateTable"; + } else if (identifierEquals("CreateInstance")) { + lexer.nextToken(); + privilege = "CreateInstance"; + } else if (identifierEquals("CreateFunction")) { + lexer.nextToken(); + privilege = "CreateFunction"; + } else if (identifierEquals("CreateResource")) { + lexer.nextToken(); + privilege = "CreateResource"; + } else if (identifierEquals("CreateJob")) { + lexer.nextToken(); + privilege = "CreateJob"; + } else if (identifierEquals("CreateVolume")) { + lexer.nextToken(); + privilege = "CreateVolume"; + } else if (identifierEquals("CreateOfflineModel")) { + lexer.nextToken(); + privilege = "CreateOfflineModel"; + } else if (identifierEquals("CreateXflow")) { + lexer.nextToken(); + privilege = "CreateXflow"; + } + + SQLExpr expr = null; + if (privilege != null) { + expr = new SQLIdentifierExpr(privilege); + } else { + expr = this.exprParser.expr(); + } + expr.setParent(parent); + privileges.add(expr); + + if (lexer.token() == Token.COMMA) { + lexer.nextToken(); + continue; + } + break; + } + } } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitor.java index 94c35484fe..7f6b41ab04 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitor.java @@ -18,6 +18,7 @@ package com.alibaba.druid.sql.dialect.odps.visitor; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddStatisticStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAnalyzeTableStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsCreateTableStatement; +import com.alibaba.druid.sql.dialect.odps.ast.OdpsGrantStmt; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsert; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsertStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsListStmt; @@ -113,4 +114,8 @@ public interface OdpsASTVisitor extends SQLASTVisitor { void endVisit(OdpsListStmt x); boolean visit(OdpsListStmt x); + + void endVisit(OdpsGrantStmt x); + + boolean visit(OdpsGrantStmt x); } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitorAdapter.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitorAdapter.java index 0ef7989246..04cab8cf4e 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitorAdapter.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsASTVisitorAdapter.java @@ -18,6 +18,7 @@ package com.alibaba.druid.sql.dialect.odps.visitor; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddStatisticStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAnalyzeTableStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsCreateTableStatement; +import com.alibaba.druid.sql.dialect.odps.ast.OdpsGrantStmt; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsert; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsertStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsListStmt; @@ -235,4 +236,14 @@ public class OdpsASTVisitorAdapter extends SQLASTVisitorAdapter implements OdpsA return true; } + @Override + public void endVisit(OdpsGrantStmt x) { + + } + + @Override + public boolean visit(OdpsGrantStmt x) { + return true; + } + } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java index 064890f762..af4b8184ae 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsOutputVisitor.java @@ -38,6 +38,7 @@ import com.alibaba.druid.sql.ast.statement.SQLTableElement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddStatisticStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAnalyzeTableStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsCreateTableStatement; +import com.alibaba.druid.sql.dialect.odps.ast.OdpsGrantStmt; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsert; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsertStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsListStmt; @@ -758,7 +759,7 @@ public class OdpsOutputVisitor extends SQLASTOutputVisitor implements OdpsASTVis @Override public void endVisit(OdpsShowGrantsStmt x) { - + } @Override @@ -768,7 +769,7 @@ public class OdpsOutputVisitor extends SQLASTOutputVisitor implements OdpsASTVis print(" FOR "); x.getUser().accept(this); } - + if (x.getObjectType() != null) { print(" ON TYPE "); x.getObjectType().accept(this); @@ -789,4 +790,55 @@ public class OdpsOutputVisitor extends SQLASTOutputVisitor implements OdpsASTVis } return false; } + + @Override + public void endVisit(OdpsGrantStmt x) { + + } + + @Override + public boolean visit(OdpsGrantStmt x) { + print("GRANT "); + if (x.isSuper()) { + print("SUPER "); + } + + if (x.isLabel()) { + print("LABEL "); + x.getLabel().accept(this);; + } else { + printAndAccept(x.getPrivileges(), ", "); + } + + if (x.getOn() != null) { + print(" ON "); + if (x.getObjectType() != null) { + print(x.getObjectType().name()); + print(' '); + } + x.getOn().accept(this); + + if (x.getColumns().size() > 0) { + print("("); + printAndAccept(x.getColumns(), ", "); + print(")"); + } + } + + if (x.getTo() != null) { + print(" TO "); + if (x.getSubjectType() != null) { + print(x.getSubjectType().name()); + print(' '); + } + x.getTo().accept(this); + } + + if (x.getExpire() != null) { + print(" WITH EXP "); + x.getExpire().accept(this); + } + + return false; + } } diff --git a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsSchemaStatVisitor.java b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsSchemaStatVisitor.java index cc2dda9398..f462f60f64 100644 --- a/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsSchemaStatVisitor.java +++ b/src/main/java/com/alibaba/druid/sql/dialect/odps/visitor/OdpsSchemaStatVisitor.java @@ -16,10 +16,12 @@ package com.alibaba.druid.sql.dialect.odps.visitor; import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement; +import com.alibaba.druid.sql.ast.statement.SQLGrantStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAddStatisticStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsAnalyzeTableStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsCreateTableStatement; +import com.alibaba.druid.sql.dialect.odps.ast.OdpsGrantStmt; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsert; import com.alibaba.druid.sql.dialect.odps.ast.OdpsInsertStatement; import com.alibaba.druid.sql.dialect.odps.ast.OdpsListStmt; @@ -238,4 +240,14 @@ public class OdpsSchemaStatVisitor extends SchemaStatVisitor implements OdpsASTV public boolean visit(OdpsListStmt x) { return true; } + + @Override + public void endVisit(OdpsGrantStmt x) { + super.endVisit((SQLGrantStatement) x); + } + + @Override + public boolean visit(OdpsGrantStmt x) { + return super.visit((SQLGrantStatement) x); + } } diff --git a/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsGrantTest.java b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsGrantTest.java new file mode 100644 index 0000000000..681d5c3c5b --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/sql/odps/OdpsGrantTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2101 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.druid.bvt.sql.odps; + +import junit.framework.TestCase; + +import org.junit.Assert; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.odps.parser.OdpsStatementParser; +import com.alibaba.druid.sql.parser.Token; + +public class OdpsGrantTest extends TestCase { + + public void test_0() throws Exception { + String sql = "grant update, Select on table adl_register_baseline_sdt to user DXP_71074213@aliyun.com"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + // System.out.println(output); + Assert.assertEquals("GRANT UPDATE, SELECT ON TABLE adl_register_baseline_sdt TO USER DXP_71074213@aliyun.com", + output); + } + + public void test_1() throws Exception { + String sql = "grant role_project_admin to aliyun$DXP_xxxxx@aliyun.com"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + // System.out.println(output); + Assert.assertEquals("GRANT role_project_admin TO aliyun$DXP_xxxxx@aliyun.com", output); + } + + public void test_2() throws Exception { + String sql = "grant super Write to user aliyun$DXP_xxxxx@aliyun.com"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + // System.out.println(output); + Assert.assertEquals("GRANT SUPER WRITE TO USER aliyun$DXP_xxxxx@aliyun.com", output); + } + + public void test_3() throws Exception { + String sql = "grant label 2 on table adl_register_baseline_sdt(c1, c2) to user aliyun$DXP_xxxxx@aliyun.com with exp 5"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + // System.out.println(output); + Assert.assertEquals("GRANT LABEL 2 ON TABLE adl_register_baseline_sdt(c1, c2) TO USER aliyun$DXP_xxxxx@aliyun.com WITH EXP 5", + output); + } + + public void test_4() throws Exception { + String sql = "grant CreateInstance, CreateResource, CreateFunction, CreateTable, List ON PROJECT test_project TO ROLE worker"; + OdpsStatementParser parser = new OdpsStatementParser(sql); + SQLStatement stmt = parser.parseStatementList().get(0); + parser.match(Token.EOF); + String output = SQLUtils.toOdpsString(stmt); + // System.out.println(output); + Assert.assertEquals("GRANT CreateInstance, CreateResource, CreateFunction, CreateTable, LIST ON PROJECT test_project TO ROLE worker", + output); + } +}