Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ This operation requires the SYSTEM-level PLUGIN privilege. You can follow the in
## Syntax

```sql
INSTALL PLUGIN FROM [source] [PROPERTIES ("key"="value", ...)]
INSTALL PLUGIN [IF NOT EXISTS] FROM [source] [PROPERTIES ("key"="value", ...)]
```

**IF NOT EXISTS**: If specified, the statement succeeds silently when the plugin is already installed instead of returning an error.

3 types of sources are supported:

```plain text
Expand Down Expand Up @@ -54,3 +56,9 @@ PROPERTIES supports setting some configurations of plugins, such as setting the
```sql
INSTALL PLUGIN FROM "http://mywebsite.com/plugin.zip" PROPERTIES("md5sum" = "73877f6029216f4314d712086a146570");
```

5. Install a plugin if it is not already installed:

```sql
INSTALL PLUGIN IF NOT EXISTS FROM "/home/users/starrocks/auditdemo.zip";
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ This operation requires the SYSTEM-level PLUGIN privilege. You can follow the in
## Syntax

```SQL
UNINSTALL PLUGIN <plugin_name>
UNINSTALL PLUGIN [IF EXISTS] <plugin_name>
```

**IF EXISTS**: If specified, the statement succeeds silently when the plugin does not exist instead of returning an error.

plugin_name can be viewed through SHOW PLUGINS command

Only non-builtin plugins can be uninstalled.
Expand All @@ -30,3 +32,9 @@ Only non-builtin plugins can be uninstalled.
```SQL
UNINSTALL PLUGIN auditdemo;
```

2. Uninstall a plugin if it exists:

```SQL
UNINSTALL PLUGIN IF EXISTS auditdemo;
```
9 changes: 9 additions & 0 deletions fe/fe-core/src/main/java/com/starrocks/plugin/PluginMgr.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ public PluginInfo installPlugin(InstallPluginStmt stmt) throws IOException, Star
}

if (checkDynamicPluginNameExist(info.getName())) {
if (stmt.isIfNotExists()) {
Comment thread
s4ch1n marked this conversation as resolved.
return null;
}
throw new StarRocksException("plugin " + info.getName() + " has already been installed.");
}

Expand All @@ -169,6 +172,12 @@ public PluginInfo installPlugin(InstallPluginStmt stmt) throws IOException, Star

public void uninstallPluginFromStmt(UninstallPluginStmt stmt) throws IOException, StarRocksException {
String pluginName = stmt.getPluginName();
if (!checkDynamicPluginNameExist(pluginName)) {
if (stmt.isIfExists()) {
Comment thread
s4ch1n marked this conversation as resolved.
return;
}
throw new DdlException("Plugin " + pluginName + " does not exist");
Comment thread
dirtysalt marked this conversation as resolved.
}
int typeId = uninstallPlugin(pluginName);

GlobalStateMgr.getCurrentState().getEditLog().logUninstallPlugin(new UninstallPluginLog(pluginName), wal -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4632,14 +4632,16 @@ public ParseNode visitShowExportStatement(com.starrocks.sql.parser.StarRocksPars
public ParseNode visitInstallPluginStatement(com.starrocks.sql.parser.StarRocksParser.InstallPluginStatementContext context) {
String pluginPath = ((Identifier) visit(context.identifierOrString())).getValue();
Map<String, String> properties = getCaseSensitiveProperties(context.properties());
return new InstallPluginStmt(pluginPath, properties, createPos(context));
boolean ifNotExists = context.IF() != null;
return new InstallPluginStmt(pluginPath, properties, ifNotExists, createPos(context));
}

@Override
public ParseNode visitUninstallPluginStatement(
com.starrocks.sql.parser.StarRocksParser.UninstallPluginStatementContext context) {
String pluginPath = ((Identifier) visit(context.identifierOrString())).getValue();
return new UninstallPluginStmt(pluginPath, createPos(context));
boolean ifExists = context.IF() != null;
return new UninstallPluginStmt(pluginPath, ifExists, createPos(context));
}

// ------------------------------------------------- File Statement ----------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@
import com.starrocks.plugin.DynamicPluginLoader;
import com.starrocks.qe.ConnectContext;
import com.starrocks.sql.ast.InstallPluginStmt;
import com.starrocks.sql.ast.StatementBase;
import com.starrocks.sql.ast.UninstallPluginStmt;
import com.starrocks.sql.parser.SqlParser;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

public class InstallPluginStmtTest {
Expand All @@ -38,4 +42,51 @@ public void testNormal() throws StarRocksException {
stmt.getProperties().get(DynamicPluginLoader.MD5SUM_KEY));
Assertions.assertEquals("http://test/test.zip", stmt.getPluginPath());
}

@Test
public void testInstallPluginIfNotExistsParsedTrue() {
String sql = "INSTALL PLUGIN IF NOT EXISTS FROM '/path/to/plugin.zip'";
List<StatementBase> stmts = SqlParser.parse(sql, 32);
InstallPluginStmt stmt = (InstallPluginStmt) stmts.get(0);
Assertions.assertTrue(stmt.isIfNotExists());
}

@Test
public void testInstallPluginWithoutIfNotExistsDefaultsFalse() {
String sql = "INSTALL PLUGIN FROM '/path/to/plugin.zip'";
List<StatementBase> stmts = SqlParser.parse(sql, 32);
InstallPluginStmt stmt = (InstallPluginStmt) stmts.get(0);
Assertions.assertFalse(stmt.isIfNotExists());
}

@Test
public void testUninstallPluginIfExistsParsedTrue() {
String sql = "UNINSTALL PLUGIN IF EXISTS my_plugin";
List<StatementBase> stmts = SqlParser.parse(sql, 32);
UninstallPluginStmt stmt = (UninstallPluginStmt) stmts.get(0);
Assertions.assertTrue(stmt.isIfExists());
}

@Test
public void testUninstallPluginWithoutIfExistsDefaultsFalse() {
String sql = "UNINSTALL PLUGIN my_plugin";
List<StatementBase> stmts = SqlParser.parse(sql, 32);
UninstallPluginStmt stmt = (UninstallPluginStmt) stmts.get(0);
Assertions.assertFalse(stmt.isIfExists());
}

@Test
public void testInstallPluginConstructorDefaultsIfNotExistsFalse() {
Map<String, String> props = Maps.newHashMap();
InstallPluginStmt stmt = new InstallPluginStmt("http://test/plugin.zip", props);
Assertions.assertFalse(stmt.isIfNotExists());
Assertions.assertEquals("http://test/plugin.zip", stmt.getPluginPath());
}

@Test
public void testUninstallPluginConstructorDefaultsIfExistsFalse() {
UninstallPluginStmt stmt = new UninstallPluginStmt("my_plugin");
Assertions.assertFalse(stmt.isIfExists());
Assertions.assertEquals("my_plugin", stmt.getPluginName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.starrocks.server.GlobalStateMgr;
import com.starrocks.sql.ast.InstallPluginStmt;
import com.starrocks.sql.ast.UninstallPluginStmt;
import com.starrocks.sql.parser.NodePosition;
import com.starrocks.utframe.UtFrameUtils;
import mockit.Mock;
import mockit.MockUp;
Expand Down Expand Up @@ -293,5 +294,43 @@ public void testUninstallPluginFromStmtMethodPluginNotExists() throws Exception
});
Assertions.assertTrue(exception.getMessage().contains("does not exist"));
}

@Test
public void testInstallPluginIfNotExistsWhenAlreadyInstalled() throws Exception {
// Pre-register the plugin so it already exists
PluginInfo pluginInfo = createMockPluginInfo();
masterPluginMgr.replayLoadDynamicPlugin(pluginInfo);
Assertions.assertTrue(pluginExists(masterPluginMgr, TEST_PLUGIN_NAME, PluginInfo.PluginType.AUDIT));

// Build stmt with IF NOT EXISTS flag set
Map<String, String> properties = Maps.newHashMap();
InstallPluginStmt stmt = new InstallPluginStmt("http://test/test.zip", properties, true, NodePosition.ZERO);

new MockUp<DynamicPluginLoader>() {
@Mock
public PluginInfo getPluginInfo() throws IOException {
return pluginInfo;
}
};

// Should silently return null — no exception, no re-install
PluginInfo result = masterPluginMgr.installPlugin(stmt);
Assertions.assertNull(result);

// Plugin should still be registered (untouched)
Assertions.assertTrue(pluginExists(masterPluginMgr, TEST_PLUGIN_NAME, PluginInfo.PluginType.AUDIT));
}

@Test
public void testUninstallPluginIfExistsWhenNotInstalled() throws Exception {
String nonExistentPlugin = "non_existent_plugin_ifexists";
Assertions.assertFalse(pluginExists(masterPluginMgr, nonExistentPlugin, PluginInfo.PluginType.AUDIT));

// Build stmt with IF EXISTS flag set
UninstallPluginStmt stmt = new UninstallPluginStmt(nonExistentPlugin, true, NodePosition.ZERO);

// Should return silently — no exception
Assertions.assertDoesNotThrow(() -> masterPluginMgr.uninstallPluginFromStmt(stmt));
}
}

61 changes: 61 additions & 0 deletions fe/fe-core/src/test/java/com/starrocks/plugin/PluginMgrTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,32 @@

package com.starrocks.plugin;

import com.google.common.collect.Maps;
import com.starrocks.common.Config;
import com.starrocks.common.util.DigitalVersion;
import com.starrocks.persist.EditLog;
import com.starrocks.persist.UninstallPluginLog;
import com.starrocks.persist.WALApplier;
import com.starrocks.plugin.PluginInfo.PluginType;
import com.starrocks.server.GlobalStateMgr;
import com.starrocks.sql.ast.InstallPluginStmt;
import com.starrocks.sql.ast.UninstallPluginStmt;
import com.starrocks.sql.parser.NodePosition;
import com.starrocks.utframe.UtFrameUtils;
import mockit.Mock;
import mockit.MockUp;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class PluginMgrTest {
Expand Down Expand Up @@ -89,4 +101,53 @@ public void testLoadPluginFail() {
assert false;
}
}

@Test
public void testInstallPluginIfNotExistsIdempotent() throws Exception {
String pluginName = "idempotent_install_plugin";
PluginInfo pluginInfo = new PluginInfo(pluginName, PluginType.AUDIT, "test");

PluginMgr pluginMgr = new PluginMgr();
pluginMgr.replayLoadDynamicPlugin(pluginInfo);

new MockUp<DynamicPluginLoader>() {
@Mock
public PluginInfo getPluginInfo() throws IOException {
return pluginInfo;
}
};

Map<String, String> props = Maps.newHashMap();
InstallPluginStmt stmt = new InstallPluginStmt("http://dummy/test.zip", props, true, NodePosition.ZERO);
PluginInfo result = pluginMgr.installPlugin(stmt);
assertNull(result);
}

@Test
public void testUninstallPluginIfExistsIdempotent() {
PluginMgr pluginMgr = new PluginMgr();
UninstallPluginStmt stmt = new UninstallPluginStmt("nonexistent_plugin", true, NodePosition.ZERO);
assertDoesNotThrow(() -> pluginMgr.uninstallPluginFromStmt(stmt));
}

@Test
public void testUninstallPluginFromStmtNormal() throws Exception {
String pluginName = "normal_uninstall_plugin";
PluginInfo pluginInfo = new PluginInfo(pluginName, PluginType.AUDIT, "test");

PluginMgr pluginMgr = new PluginMgr();
pluginMgr.replayLoadDynamicPlugin(pluginInfo);

new MockUp<EditLog>() {
@Mock
public void logUninstallPlugin(UninstallPluginLog log, WALApplier walApplier) {
walApplier.apply(log);
}
};

UninstallPluginStmt stmt = new UninstallPluginStmt(pluginName);
pluginMgr.uninstallPluginFromStmt(stmt);
assertFalse(pluginMgr.getAllDynamicPluginInfo()
.stream().anyMatch(p -> p.getName().equals(pluginName)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2172,11 +2172,11 @@ showExportStatement
// ------------------------------------------- Plugin Statement --------------------------------------------------------

installPluginStatement
: INSTALL PLUGIN FROM identifierOrString properties?
: INSTALL PLUGIN (IF NOT EXISTS)? FROM identifierOrString properties?
;

uninstallPluginStatement
: UNINSTALL PLUGIN identifierOrString
: UNINSTALL PLUGIN (IF EXISTS)? identifierOrString
;

// ------------------------------------------- File Statement ----------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ public class InstallPluginStmt extends DdlStmt {

private final String pluginPath;
private final Map<String, String> properties;
private final boolean ifNotExists;

public InstallPluginStmt(String pluginPath, Map<String, String> properties) {
this(pluginPath, properties, NodePosition.ZERO);
}

public InstallPluginStmt(String pluginPath, Map<String, String> properties, NodePosition pos) {
this(pluginPath, properties, false, pos);
}

public InstallPluginStmt(String pluginPath, Map<String, String> properties, boolean ifNotExists, NodePosition pos) {
super(pos);
this.pluginPath = pluginPath;
this.properties = properties;
this.ifNotExists = ifNotExists;
}

public String getPluginPath() {
Expand All @@ -42,6 +48,10 @@ public Map<String, String> getProperties() {
return properties;
}

public boolean isIfNotExists() {
return ifNotExists;
}

@Override
public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
return visitor.visitInstallPluginStatement(this, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,30 @@
public class UninstallPluginStmt extends DdlStmt {

private final String pluginName;
private final boolean ifExists;

public UninstallPluginStmt(String pluginName) {
this(pluginName, NodePosition.ZERO);
this(pluginName, false, NodePosition.ZERO);
}

public UninstallPluginStmt(String pluginName, NodePosition pos) {
this(pluginName, false, pos);
}

public UninstallPluginStmt(String pluginName, boolean ifExists, NodePosition pos) {
super(pos);
this.pluginName = pluginName;
this.ifExists = ifExists;
}

public String getPluginName() {
return pluginName;
}

public boolean isIfExists() {
return ifExists;
}

@Override
public <R, C> R accept(AstVisitor<R, C> visitor, C context) {
return visitor.visitUninstallPluginStatement(this, context);
Expand Down
Loading