feat(oceanbase-oracle): append object grants in table DDL

This commit is contained in:
Mo-Moore authored and GitHub committed 2026-09-14 17:52:13 +08:00
1 parent 2e3b7da533
commit a7a2e2f434
5 files changed
+555 -2

No files matched your search

@@ -219,6 +219,118 @@ public final class DdlBuilder {
return ddl.toString();
}
/**
* Appends trailing SQL (e.g. GRANT statements) after a CREATE TABLE DDL script.
* Returns the original DDL unchanged when {@code trailingSql} is blank.
*/
public static String appendTrailingSql(String ddl, String trailingSql) {
if (!notBlank(trailingSql)) {
return ddl == null ? "" : ddl;
}
String base = ddl == null ? "" : ddl.trim();
if (base.isEmpty()) {
return trailingSql.trim();
}
if (!base.endsWith(";")) {
base = base + ";";
}
String grants = trailingSql.trim();
return base + "\n\n" + grants + (grants.endsWith(";") ? "" : ";");
}
/**
* Builds Oracle-style object GRANT statements for table and column privileges.
* Privileges are grouped by grantee / grantable / privilege(+columns).
*/
public static String buildOracleObjectGrantSql(
String schema,
String table,
List<OracleObjectPrivilege> privileges
) {
if (privileges == null || privileges.isEmpty() || !notBlank(table)) {
return "";
}
String tableRef = qualifiedName(schema, table, false);
Map<String, List<String>> tablePrivileges = new LinkedHashMap<>();
Map<String, List<String>> columnPrivileges = new LinkedHashMap<>();
for (OracleObjectPrivilege privilege : privileges) {
if (privilege == null || !notBlank(privilege.grantee()) || !notBlank(privilege.privilege())) {
continue;
}
String privilegeName = privilege.privilege().trim().toUpperCase(Locale.ROOT);
String grantableKey = privilege.grantable() ? "1" : "0";
if (notBlank(privilege.columnName())) {
String key = privilege.grantee() + "\u0000" + privilegeName + "\u0000" + grantableKey;
columnPrivileges
.computeIfAbsent(key, ignored -> new ArrayList<>())
.add(privilege.columnName().trim());
} else {
String key = privilege.grantee() + "\u0000" + grantableKey;
List<String> names = tablePrivileges.computeIfAbsent(key, ignored -> new ArrayList<>());
if (!names.contains(privilegeName)) {
names.add(privilegeName);
}
}
}
List<String> statements = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : tablePrivileges.entrySet()) {
String[] parts = entry.getKey().split("\u0000", -1);
String grantee = parts[0];
boolean grantable = "1".equals(parts[1]);
statements.add(formatOracleGrant(
join(entry.getValue(), ", "),
null,
tableRef,
grantee,
grantable
));
}
for (Map.Entry<String, List<String>> entry : columnPrivileges.entrySet()) {
String[] parts = entry.getKey().split("\u0000", -1);
String grantee = parts[0];
String privilegeName = parts[1];
boolean grantable = "1".equals(parts[2]);
List<String> columns = new ArrayList<>();
for (String column : entry.getValue()) {
String quoted = quoteIdent(column, false);
if (!columns.contains(quoted)) {
columns.add(quoted);
}
}
statements.add(formatOracleGrant(
privilegeName,
join(columns, ", "),
tableRef,
grantee,
grantable
));
}
return join(statements, "\n");
}
private static String formatOracleGrant(
String privilegeList,
String columnList,
String tableRef,
String grantee,
boolean grantable
) {
StringBuilder statement = new StringBuilder("GRANT ");
statement.append(privilegeList);
if (notBlank(columnList)) {
statement.append(" (").append(columnList).append(")");
}
statement.append(" ON ").append(tableRef);
statement.append(" TO ").append(quoteIdent(grantee, false));
if (grantable) {
statement.append(" WITH GRANT OPTION");
}
statement.append(";");
return statement.toString();
}
private static List<ForeignKeyGroup> groupForeignKeys(List<ForeignKeyInfo> foreignKeys) {
List<ForeignKeyGroup> result = new ArrayList<>();
Map<String, ForeignKeyGroup> namedGroups = new LinkedHashMap<>();
@@ -0,0 +1,38 @@
package com.dbx.agent;
/**
* Oracle-compatible object privilege row used when reconstructing GRANT DDL.
*/
public final class OracleObjectPrivilege {
private final String grantee;
private final String privilege;
private final boolean grantable;
private final String columnName;
public OracleObjectPrivilege(String grantee, String privilege, boolean grantable) {
this(grantee, privilege, grantable, null);
}
public OracleObjectPrivilege(String grantee, String privilege, boolean grantable, String columnName) {
this.grantee = grantee;
this.privilege = privilege;
this.grantable = grantable;
this.columnName = columnName;
}
public String grantee() {
return grantee;
}
public String privilege() {
return privilege;
}
public boolean grantable() {
return grantable;
}
public String columnName() {
return columnName;
}
}
@@ -750,6 +750,36 @@ class CommonJavaCompatibilityTest {
assertTrue(ddl.contains("\"NAME\" NVARCHAR(100) NOT NULL"));
}
@Test
void appendsOracleObjectGrantSqlAfterTableDdl() {
String ddl = DdlBuilder.buildTableDdl(
"APP",
"USERS",
Collections.singletonList(new ColumnInfo("ID", "NUMBER", false, null, true)),
Collections.emptyList(),
Collections.emptyList()
);
String grants = DdlBuilder.buildOracleObjectGrantSql(
"APP",
"USERS",
Arrays.asList(
new OracleObjectPrivilege("READER", "SELECT", false),
new OracleObjectPrivilege("READER", "INSERT", false),
new OracleObjectPrivilege("ADMIN", "SELECT", true),
new OracleObjectPrivilege("ANALYST", "UPDATE", false, "NAME")
)
);
String combined = DdlBuilder.appendTrailingSql(ddl, grants);
assertTrue(combined.contains("CREATE TABLE \"APP\".\"USERS\""));
assertTrue(combined.contains("GRANT SELECT, INSERT ON \"APP\".\"USERS\" TO \"READER\";"));
assertTrue(combined.contains("GRANT SELECT ON \"APP\".\"USERS\" TO \"ADMIN\" WITH GRANT OPTION;"));
assertTrue(combined.contains("GRANT UPDATE (\"NAME\") ON \"APP\".\"USERS\" TO \"ANALYST\";"));
assertEquals("", DdlBuilder.buildOracleObjectGrantSql("APP", "USERS", Collections.emptyList()));
assertEquals(ddl, DdlBuilder.appendTrailingSql(ddl, " "));
}
private static class MinimalAgent implements DatabaseAgent {
@Override
public void connect(ConnectParams params) {