Skip to content

Commit 13f2453

Browse files
committed
support show backup in nereids
1 parent 7cd9757 commit 13f2453

File tree

7 files changed

+287
-1
lines changed

7 files changed

+287
-1
lines changed

fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisParser.g4

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ supportedShowStatement
275275
: SHOW statementScope? VARIABLES wildWhere? #showVariables
276276
| SHOW AUTHORS #showAuthors
277277
| SHOW CREATE (DATABASE | SCHEMA) name=multipartIdentifier #showCreateDatabase
278+
| SHOW BACKUP ((FROM | IN) database=multipartIdentifier)? wildWhere? #showBackup
278279
| SHOW BROKER #showBroker
279280
| SHOW DYNAMIC PARTITION TABLES ((FROM | IN) database=multipartIdentifier)? #showDynamicPartition
280281
| SHOW EVENTS ((FROM | IN) database=multipartIdentifier)? wildWhere? #showEvents
@@ -401,7 +402,6 @@ unsupportedShowStatement
401402
| SHOW TABLET tabletId=INTEGER_VALUE #showTabletId
402403
| SHOW TABLETS FROM tableName=multipartIdentifier partitionSpec?
403404
wildWhere? sortClause? limitClause? #showTabletsFromTable
404-
| SHOW BACKUP ((FROM | IN) database=multipartIdentifier)? wildWhere? #showBackup
405405
| SHOW BRIEF? RESTORE ((FROM | IN) database=multipartIdentifier)? wildWhere? #showRestore
406406
| SHOW RESOURCES wildWhere? sortClause? limitClause? #showResources
407407
| SHOW WORKLOAD GROUPS wildWhere? #showWorkloadGroups

fe/fe-core/src/main/java/org/apache/doris/nereids/parser/LogicalPlanBuilder.java

+16
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
import org.apache.doris.nereids.DorisParser.ShowAnalyzeContext;
298298
import org.apache.doris.nereids.DorisParser.ShowAuthorsContext;
299299
import org.apache.doris.nereids.DorisParser.ShowBackendsContext;
300+
import org.apache.doris.nereids.DorisParser.ShowBackupContext;
300301
import org.apache.doris.nereids.DorisParser.ShowBrokerContext;
301302
import org.apache.doris.nereids.DorisParser.ShowCharsetContext;
302303
import org.apache.doris.nereids.DorisParser.ShowCollationContext;
@@ -600,6 +601,7 @@
600601
import org.apache.doris.nereids.trees.plans.commands.ShowAnalyzeCommand;
601602
import org.apache.doris.nereids.trees.plans.commands.ShowAuthorsCommand;
602603
import org.apache.doris.nereids.trees.plans.commands.ShowBackendsCommand;
604+
import org.apache.doris.nereids.trees.plans.commands.ShowBackupCommand;
603605
import org.apache.doris.nereids.trees.plans.commands.ShowBrokerCommand;
604606
import org.apache.doris.nereids.trees.plans.commands.ShowCatalogCommand;
605607
import org.apache.doris.nereids.trees.plans.commands.ShowCharsetCommand;
@@ -4960,6 +4962,20 @@ public LogicalPlan visitShowBackends(ShowBackendsContext ctx) {
49604962
return new ShowBackendsCommand();
49614963
}
49624964

4965+
@Override
4966+
public LogicalPlan visitShowBackup(ShowBackupContext ctx) {
4967+
String dbName = null;
4968+
Expression wildWhere = null;
4969+
if (ctx.database != null) {
4970+
List<String> nameParts = visitMultipartIdentifier(ctx.database);
4971+
dbName = nameParts.get(0);
4972+
}
4973+
if (ctx.wildWhere() != null) {
4974+
wildWhere = getWildWhere(ctx.wildWhere());
4975+
}
4976+
return new ShowBackupCommand(dbName, wildWhere);
4977+
}
4978+
49634979
@Override
49644980
public LogicalPlan visitShowPlugins(ShowPluginsContext ctx) {
49654981
return new ShowPluginsCommand();

fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/PlanType.java

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ public enum PlanType {
221221
SHOW_ANALYZE_COMMAND,
222222
SHOW_QUEUED_ANALYZE_JOBS_COMMAND,
223223
SHOW_BACKENDS_COMMAND,
224+
SHOW_BACKUP_COMMAND,
224225
SHOW_BLOCK_RULE_COMMAND,
225226
SHOW_BROKER_COMMAND,
226227
SHOW_CATALOG_COMMAND,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.doris.nereids.trees.plans.commands;
19+
20+
import org.apache.doris.backup.AbstractJob;
21+
import org.apache.doris.backup.BackupJob;
22+
import org.apache.doris.catalog.Column;
23+
import org.apache.doris.catalog.DatabaseIf;
24+
import org.apache.doris.catalog.Env;
25+
import org.apache.doris.catalog.ScalarType;
26+
import org.apache.doris.common.AnalysisException;
27+
import org.apache.doris.common.CaseSensibility;
28+
import org.apache.doris.common.ErrorCode;
29+
import org.apache.doris.common.ErrorReport;
30+
import org.apache.doris.common.PatternMatcher;
31+
import org.apache.doris.common.PatternMatcherWrapper;
32+
import org.apache.doris.common.UserException;
33+
import org.apache.doris.datasource.InternalCatalog;
34+
import org.apache.doris.mysql.privilege.PrivPredicate;
35+
import org.apache.doris.nereids.analyzer.UnboundSlot;
36+
import org.apache.doris.nereids.trees.expressions.EqualTo;
37+
import org.apache.doris.nereids.trees.expressions.Expression;
38+
import org.apache.doris.nereids.trees.expressions.Like;
39+
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
40+
import org.apache.doris.nereids.trees.plans.PlanType;
41+
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
42+
import org.apache.doris.qe.ConnectContext;
43+
import org.apache.doris.qe.ShowResultSet;
44+
import org.apache.doris.qe.ShowResultSetMetaData;
45+
import org.apache.doris.qe.StmtExecutor;
46+
47+
import com.google.common.base.Strings;
48+
import com.google.common.collect.ImmutableList;
49+
50+
import java.util.List;
51+
import java.util.function.Predicate;
52+
import java.util.stream.Collectors;
53+
54+
/**
55+
* show backup command
56+
*/
57+
public class ShowBackupCommand extends ShowCommand {
58+
public static final ImmutableList<String> TITLE_NAMES = new ImmutableList.Builder<String>()
59+
.add("JobId").add("SnapshotName").add("DbName").add("State").add("BackupObjs").add("CreateTime")
60+
.add("SnapshotFinishedTime").add("UploadFinishedTime").add("FinishedTime").add("UnfinishedTasks")
61+
.add("Progress").add("TaskErrMsg").add("Status").add("Timeout")
62+
.build();
63+
64+
private String dbName;
65+
private Expression where;
66+
private boolean isAccurateMatch;
67+
private String snapshotName;
68+
69+
/**
70+
* constructor
71+
*/
72+
public ShowBackupCommand(String dbName, Expression where) {
73+
super(PlanType.SHOW_BACKUP_COMMAND);
74+
this.dbName = dbName;
75+
this.where = where;
76+
}
77+
78+
public String getDbName() {
79+
return dbName;
80+
}
81+
82+
/**
83+
* get metadata
84+
*/
85+
public ShowResultSetMetaData getMetaData() {
86+
ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder();
87+
for (String title : TITLE_NAMES) {
88+
builder.addColumn(new Column(title, ScalarType.createVarchar(30)));
89+
}
90+
return builder.build();
91+
}
92+
93+
/**
94+
* get label predicate for show backup
95+
*/
96+
public Predicate<String> getSnapshotPredicate() throws AnalysisException {
97+
if (null == where) {
98+
return label -> true;
99+
}
100+
if (isAccurateMatch) {
101+
return CaseSensibility.LABEL.getCaseSensibility()
102+
? label -> label.equals(snapshotName) : label -> label.equalsIgnoreCase(snapshotName);
103+
} else {
104+
PatternMatcher patternMatcher = PatternMatcherWrapper.createMysqlPattern(
105+
snapshotName, CaseSensibility.LABEL.getCaseSensibility());
106+
return patternMatcher::match;
107+
}
108+
}
109+
110+
/**
111+
* validate
112+
*/
113+
private boolean validate(ConnectContext ctx) throws UserException {
114+
if (Strings.isNullOrEmpty(dbName)) {
115+
dbName = ctx.getDatabase();
116+
if (Strings.isNullOrEmpty(dbName)) {
117+
throw new AnalysisException("No database selected");
118+
}
119+
}
120+
121+
// check auth
122+
if (!Env.getCurrentEnv().getAccessManager()
123+
.checkDbPriv(ConnectContext.get(), InternalCatalog.INTERNAL_CATALOG_NAME, dbName, PrivPredicate.LOAD)) {
124+
ErrorReport.reportAnalysisException(ErrorCode.ERR_DBACCESS_DENIED_ERROR,
125+
ConnectContext.get().getQualifiedUser(), dbName);
126+
}
127+
128+
// SQL may be like : show backup from your_db_name; there is no where clause.
129+
if (where == null) {
130+
return true;
131+
}
132+
133+
if (!(where instanceof Like) && !(where instanceof EqualTo)) {
134+
return false;
135+
}
136+
137+
if (where instanceof EqualTo) {
138+
isAccurateMatch = true;
139+
}
140+
141+
// left child
142+
if (!(where.child(0) instanceof UnboundSlot)) {
143+
return false;
144+
}
145+
String leftKey = ((UnboundSlot) where.child(0)).getName();
146+
if (!"snapshotname".equalsIgnoreCase(leftKey)) {
147+
return false;
148+
}
149+
150+
// right child
151+
if (!(where.child(1) instanceof StringLikeLiteral)) {
152+
return false;
153+
}
154+
snapshotName = ((StringLikeLiteral) where.child(1)).getStringValue();
155+
if (Strings.isNullOrEmpty(snapshotName)) {
156+
return false;
157+
}
158+
159+
return true;
160+
}
161+
162+
/**
163+
* handle show backup
164+
*/
165+
private ShowResultSet handleShowBackup(ConnectContext ctx, StmtExecutor executor) throws Exception {
166+
boolean valid = validate(ctx);
167+
if (!valid) {
168+
throw new AnalysisException("Where clause should like: SnapshotName = \"your_snapshot_name\", "
169+
+ " or SnapshotName LIKE \"matcher\"");
170+
}
171+
172+
DatabaseIf database = ctx.getCurrentCatalog().getDbOrAnalysisException(dbName);
173+
List<AbstractJob> jobs = Env.getCurrentEnv().getBackupHandler()
174+
.getJobs(database.getId(), getSnapshotPredicate());
175+
List<BackupJob> backupJobs = jobs.stream().filter(job -> job instanceof BackupJob)
176+
.map(job -> (BackupJob) job).collect(Collectors.toList());
177+
List<List<String>> infos = backupJobs.stream().map(BackupJob::getInfo).collect(Collectors.toList());
178+
179+
return new ShowResultSet(getMetaData(), infos);
180+
}
181+
182+
@Override
183+
public ShowResultSet doRun(ConnectContext ctx, StmtExecutor executor) throws Exception {
184+
return handleShowBackup(ctx, executor);
185+
}
186+
187+
@Override
188+
public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
189+
return visitor.visitShowBackupCommand(this, context);
190+
}
191+
}

fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/visitor/CommandVisitor.java

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
import org.apache.doris.nereids.trees.plans.commands.ShowAnalyzeCommand;
101101
import org.apache.doris.nereids.trees.plans.commands.ShowAuthorsCommand;
102102
import org.apache.doris.nereids.trees.plans.commands.ShowBackendsCommand;
103+
import org.apache.doris.nereids.trees.plans.commands.ShowBackupCommand;
103104
import org.apache.doris.nereids.trees.plans.commands.ShowBrokerCommand;
104105
import org.apache.doris.nereids.trees.plans.commands.ShowCatalogCommand;
105106
import org.apache.doris.nereids.trees.plans.commands.ShowCharsetCommand;
@@ -508,6 +509,10 @@ default R visitShowBackendsCommand(ShowBackendsCommand showBackendsCommand, C co
508509
return visitCommand(showBackendsCommand, context);
509510
}
510511

512+
default R visitShowBackupCommand(ShowBackupCommand showBackupCommand, C context) {
513+
return visitCommand(showBackupCommand, context);
514+
}
515+
511516
default R visitShowCreateTableCommand(ShowCreateTableCommand showCreateTableCommand, C context) {
512517
return visitCommand(showCreateTableCommand, context);
513518
}

regression-test/pipeline/cloud_p0/conf/regression-conf-custom.groovy

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ excludeSuites = "000_the_start_sentinel_do_not_touch," + // keep this line as th
4747
"test_topn_fault_injection," +
4848
"auto_partition_in_partition_prune," + // inserted data in too many tablets, txn to large. not suitable for cloud.
4949
"one_col_range_partition," + // inserted data in too many tablets, txn to large. not suitable for cloud.
50+
"test_nereids_show_backup," +
5051
"zzz_the_end_sentinel_do_not_touch" // keep this line as the last line
5152

5253
excludeDirectories = "000_the_start_sentinel_do_not_touch," + // keep this line as the first line
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
suite("test_nereids_show_backup") {
19+
String suiteName = "test_show_backup"
20+
String repoName = "repo_" + UUID.randomUUID().toString().replace("-", "")
21+
String dbName = "${suiteName}_db"
22+
String tableName = "${suiteName}_table"
23+
String snapshotName = "${suiteName}_snapshot"
24+
25+
def syncer = getSyncer()
26+
syncer.createS3Repository(repoName)
27+
28+
sql "CREATE DATABASE IF NOT EXISTS ${dbName}"
29+
sql "DROP TABLE IF EXISTS ${dbName}.${tableName}"
30+
sql """
31+
CREATE TABLE ${dbName}.${tableName} (
32+
`id` LARGEINT NOT NULL,
33+
`count` LARGEINT SUM DEFAULT "0")
34+
AGGREGATE KEY(`id`)
35+
DISTRIBUTED BY HASH(`id`) BUCKETS 2
36+
PROPERTIES
37+
(
38+
"replication_num" = "1"
39+
)
40+
"""
41+
42+
List<String> values = []
43+
for (int i = 1; i <= 10; ++i) {
44+
values.add("(${i}, ${i})")
45+
}
46+
sql "INSERT INTO ${dbName}.${tableName} VALUES ${values.join(",")}"
47+
def result = sql "SELECT * FROM ${dbName}.${tableName}"
48+
assertEquals(result.size(), values.size());
49+
50+
sql """
51+
BACKUP SNAPSHOT ${dbName}.${snapshotName}
52+
TO `${repoName}`
53+
ON (${tableName})
54+
"""
55+
56+
syncer.waitSnapshotFinish(dbName)
57+
58+
def snapshot = syncer.getSnapshotTimestamp(repoName, snapshotName)
59+
assertTrue(snapshot != null)
60+
61+
checkNereidsExecute("show backup")
62+
checkNereidsExecute("show backup from ${dbName}")
63+
checkNereidsExecute("show backup in ${dbName}")
64+
checkNereidsExecute("""show backup from ${dbName} where SnapshotName = "${snapshotName}" """)
65+
checkNereidsExecute("""show backup in ${dbName} where SnapshotName = "${snapshotName}" """)
66+
checkNereidsExecute("show backup from ${dbName} where SnapshotName like 'test%'")
67+
checkNereidsExecute("show backup in ${dbName} where SnapshotName like 'test%'")
68+
69+
sql "DROP TABLE ${dbName}.${tableName} FORCE"
70+
sql "DROP DATABASE ${dbName} FORCE"
71+
sql "DROP REPOSITORY `${repoName}`"
72+
}

0 commit comments

Comments
 (0)