Skip to content

Commit

Permalink
[Feature] delete syntax support using clause (StarRocks#13757)
Browse files Browse the repository at this point in the history
This PR supports using clause in delete syntax, so user can delete rows specified by join with other tables. Like in https://www.postgresql.org/docs/current/sql-delete.html

DELETE FROM films USING producers
  WHERE producer_id = producers.id AND producers.name = 'foo';
  • Loading branch information
decster authored Nov 24, 2022
1 parent 287c26c commit ca4e903
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 294 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private DeleteJob createJob(DeleteStmt stmt, List<Predicate> conditions, Databas
}

// get partitions
List<String> partitionNames = stmt.getPartitionNames();
List<String> partitionNames = stmt.getPartitionNamesList();
Preconditions.checkState(partitionNames != null);
boolean noPartitionSpecified = partitionNames.isEmpty();
if (noPartitionSpecified) {
Expand Down
6 changes: 3 additions & 3 deletions fe/fe-core/src/main/java/com/starrocks/qe/StmtExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ public void analyze(TQueryOptions tQueryOptions) throws UserException {

// Because this is called by other thread
public void cancel() {
if (parsedStmt instanceof DeleteStmt && !((DeleteStmt) parsedStmt).supportNewPlanner()) {
if (parsedStmt instanceof DeleteStmt && ((DeleteStmt) parsedStmt).shouldHandledByDeleteHandler()) {
DeleteStmt deleteStmt = (DeleteStmt) parsedStmt;
long jobId = deleteStmt.getJobId();
if (jobId != -1) {
Expand Down Expand Up @@ -673,7 +673,7 @@ private void handleKill() throws DdlException {
// Only user itself and user with admin priv can kill connection
if (!killCtx.getQualifiedUser().equals(ConnectContext.get().getQualifiedUser())
&& !GlobalStateMgr.getCurrentState().getAuth().checkGlobalPriv(ConnectContext.get(),
PrivPredicate.ADMIN)) {
PrivPredicate.ADMIN)) {
ErrorReport.reportDdlException(ErrorCode.ERR_KILL_DENIED_ERROR, id);
}
}
Expand Down Expand Up @@ -1183,7 +1183,7 @@ public void handleDMLStmt(ExecPlan execPlan, DmlStmt stmt) throws Exception {
}

// special handling for delete of non-primary key table, using old handler
if (stmt instanceof DeleteStmt && !((DeleteStmt) stmt).supportNewPlanner()) {
if (stmt instanceof DeleteStmt && ((DeleteStmt) stmt).shouldHandledByDeleteHandler()) {
try {
context.getGlobalStateMgr().getDeleteHandler().process((DeleteStmt) stmt);
context.getState().setOk();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

public class DeletePlanner {
public ExecPlan plan(DeleteStmt deleteStatement, ConnectContext session) {
if (!deleteStatement.supportNewPlanner()) {
if (deleteStatement.shouldHandledByDeleteHandler()) {
// executor will use DeleteHandler to handle delete statement
// so just return empty plan here
return null;
Expand Down
115 changes: 105 additions & 10 deletions fe/fe-core/src/main/java/com/starrocks/sql/analyzer/DeleteAnalyzer.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
// This file is licensed under the Elastic License 2.0. Copyright 2021-present, StarRocks Inc.
package com.starrocks.sql.analyzer;

import com.starrocks.analysis.Analyzer;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.starrocks.analysis.BinaryPredicate;
import com.starrocks.analysis.CompoundPredicate;
import com.starrocks.analysis.Expr;
import com.starrocks.analysis.InPredicate;
import com.starrocks.analysis.IntLiteral;
import com.starrocks.analysis.IsNullPredicate;
import com.starrocks.analysis.LiteralExpr;
import com.starrocks.analysis.Predicate;
import com.starrocks.analysis.SlotRef;
import com.starrocks.analysis.TableName;
import com.starrocks.catalog.Column;
Expand All @@ -11,10 +19,14 @@
import com.starrocks.catalog.OlapTable;
import com.starrocks.catalog.Table;
import com.starrocks.catalog.Type;
import com.starrocks.common.Config;
import com.starrocks.load.Load;
import com.starrocks.qe.ConnectContext;
import com.starrocks.sql.ast.DeleteStmt;
import com.starrocks.sql.ast.JoinRelation;
import com.starrocks.sql.ast.PartitionNames;
import com.starrocks.sql.ast.QueryStatement;
import com.starrocks.sql.ast.Relation;
import com.starrocks.sql.ast.SelectList;
import com.starrocks.sql.ast.SelectListItem;
import com.starrocks.sql.ast.SelectRelation;
Expand All @@ -23,9 +35,92 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;

public class DeleteAnalyzer {
private static final Logger LOG = LogManager.getLogger(DeleteAnalyzer.class);

private static void analyzePredicate(Expr predicate, List<Predicate> deleteConditions) {
if (predicate instanceof BinaryPredicate) {
BinaryPredicate binaryPredicate = (BinaryPredicate) predicate;
Expr leftExpr = binaryPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new SemanticException("Left expr of binary predicate should be column name");
}
Expr rightExpr = binaryPredicate.getChild(1);
if (!(rightExpr instanceof LiteralExpr)) {
throw new SemanticException("Right expr of binary predicate should be value");
}
deleteConditions.add(binaryPredicate);
} else if (predicate instanceof CompoundPredicate) {
CompoundPredicate compoundPredicate = (CompoundPredicate) predicate;
if (compoundPredicate.getOp() != CompoundPredicate.Operator.AND) {
throw new SemanticException("Compound predicate's op should be AND");
}

analyzePredicate(compoundPredicate.getChild(0), deleteConditions);
analyzePredicate(compoundPredicate.getChild(1), deleteConditions);
} else if (predicate instanceof IsNullPredicate) {
IsNullPredicate isNullPredicate = (IsNullPredicate) predicate;
Expr leftExpr = isNullPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new SemanticException("Left expr of is_null predicate should be column name");
}
deleteConditions.add(isNullPredicate);
} else if (predicate instanceof InPredicate) {
InPredicate inPredicate = (InPredicate) predicate;
Expr leftExpr = inPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new SemanticException("Left expr of binary predicate should be column name");
}
int inElementNum = inPredicate.getInElementNum();
int maxAllowedInElementNumOfDelete = Config.max_allowed_in_element_num_of_delete;
if (inElementNum > maxAllowedInElementNumOfDelete) {
throw new SemanticException("Element num of predicate should not be more than " +
maxAllowedInElementNumOfDelete);
}
for (int i = 1; i <= inElementNum; i++) {
Expr expr = inPredicate.getChild(i);
if (!(expr instanceof LiteralExpr)) {
throw new SemanticException("Child of in predicate should be value");
}
}
deleteConditions.add(inPredicate);
} else {
throw new SemanticException("Where clause only supports compound predicate, binary predicate, " +
"is_null predicate and in predicate");
}
}

private static void analyzeNonPrimaryKey(DeleteStmt deleteStatement) {
PartitionNames partitionNames = deleteStatement.getPartitionNames();
if (partitionNames != null) {
if (partitionNames.isTemp()) {
throw new SemanticException("Do not support deleting temp partitions");
}
List<String> names = partitionNames.getPartitionNames();
if (names.isEmpty()) {
throw new SemanticException("No partition specifed in partition lists");
}
// check if partition name is not empty string
if (names.stream().anyMatch(entity -> Strings.isNullOrEmpty(entity))) {
throw new SemanticException("there are empty partition name");
}
}

if (deleteStatement.getUsingRelations() != null) {
throw new SemanticException("Do not support `using` clause in non-primary table");
}

if (deleteStatement.getWherePredicate() == null) {
throw new SemanticException("Where clause is not set");
}

List<Predicate> deleteConditions = Lists.newLinkedList();
analyzePredicate(deleteStatement.getWherePredicate(), deleteConditions);
deleteStatement.setDeleteConditions(deleteConditions);
}

public static void analyze(DeleteStmt deleteStatement, ConnectContext session) {
TableName tableName = deleteStatement.getTableName();
MetaUtils.normalizationTableName(session, tableName);
Expand All @@ -40,20 +135,15 @@ public static void analyze(DeleteStmt deleteStatement, ConnectContext session) {
}

if (!(table instanceof OlapTable && ((OlapTable) table).getKeysType() == KeysType.PRIMARY_KEYS)) {
try {
deleteStatement.analyze(new Analyzer(session.getGlobalStateMgr(), session));
} catch (Exception e) {
LOG.warn("Analyze DeleteStmt using old analyzer failed", e);
throw new SemanticException("Analyze DeleteStmt using old analyzer failed: " + e.getMessage());
}
analyzeNonPrimaryKey(deleteStatement);
return;
}

deleteStatement.setTable(table);
if (deleteStatement.getWherePredicate() == null) {
throw new SemanticException("Delete must specify where clause to prevent full table delete");
}
if (deleteStatement.getPartitionNames() != null && deleteStatement.getPartitionNames().size() > 0) {
if (deleteStatement.getPartitionNamesList() != null && deleteStatement.getPartitionNamesList().size() > 0) {
throw new SemanticException("Delete for primary key table do not support specifying partitions");
}

Expand All @@ -73,9 +163,14 @@ public static void analyze(DeleteStmt deleteStatement, ConnectContext session) {
throw new SemanticException("analyze delete failed", e);
}

TableRelation tableRelation = new TableRelation(tableName);
Relation relation = new TableRelation(tableName);
if (deleteStatement.getUsingRelations() != null) {
for (Relation r : deleteStatement.getUsingRelations()) {
relation = new JoinRelation(null, relation, r, null, false);
}
}
SelectRelation selectRelation =
new SelectRelation(selectList, tableRelation, deleteStatement.getWherePredicate(), null, null);
new SelectRelation(selectList, relation, deleteStatement.getWherePredicate(), null, null);
QueryStatement queryStatement = new QueryStatement(selectRelation);
queryStatement.setIsExplain(deleteStatement.isExplain(), deleteStatement.getExplainLevel());
new QueryAnalyzer(session).analyze(queryStatement);
Expand Down
125 changes: 22 additions & 103 deletions fe/fe-core/src/main/java/com/starrocks/sql/ast/DeleteStmt.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,38 @@

package com.starrocks.sql.ast;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.starrocks.analysis.Analyzer;
import com.starrocks.analysis.BinaryPredicate;
import com.starrocks.analysis.CompoundPredicate;
import com.starrocks.analysis.CompoundPredicate.Operator;
import com.starrocks.analysis.Expr;
import com.starrocks.analysis.InPredicate;
import com.starrocks.analysis.IsNullPredicate;
import com.starrocks.analysis.LiteralExpr;
import com.starrocks.analysis.Predicate;
import com.starrocks.analysis.SlotRef;
import com.starrocks.analysis.TableName;
import com.starrocks.catalog.Table;
import com.starrocks.common.AnalysisException;
import com.starrocks.common.Config;
import com.starrocks.common.UserException;

import java.util.List;

public class DeleteStmt extends DmlStmt {
private final TableName tblName;
private final PartitionNames partitionNames;
private final List<Relation> usingRelations;
private final Expr wherePredicate;

// fields for new planer, primary key table
private Table table;
private QueryStatement queryStatement;

// fields for old planer, non-primary key table
private final List<Predicate> deleteConditions;
private List<Predicate> deleteConditions;
// Each deleteStmt corresponds to a DeleteJob.
// The JobID is generated here for easy correlation when cancel Delete
private long jobId = -1;

public DeleteStmt(TableName tableName, PartitionNames partitionNames, Expr wherePredicate) {
this(tableName, partitionNames, null, wherePredicate);
}

public DeleteStmt(TableName tableName, PartitionNames partitionNames, List<Relation> usingRelations, Expr wherePredicate) {
this.tblName = tableName;
this.partitionNames = partitionNames;
this.usingRelations = usingRelations;
this.wherePredicate = wherePredicate;
this.deleteConditions = Lists.newLinkedList();
}
Expand All @@ -80,105 +74,30 @@ public Expr getWherePredicate() {
return wherePredicate;
}

public List<String> getPartitionNames() {
public List<String> getPartitionNamesList() {
return partitionNames == null ? Lists.newArrayList() : partitionNames.getPartitionNames();
}

public PartitionNames getPartitionNames() {
return partitionNames;
}

public List<Relation> getUsingRelations() {
return usingRelations;
}

public List<Predicate> getDeleteConditions() {
return deleteConditions;
}

@Override
public void analyze(Analyzer analyzer) throws UserException {
if (tblName == null) {
throw new AnalysisException("Table is not set");
}

tblName.analyze(analyzer);

if (partitionNames != null) {
partitionNames.analyze(analyzer);
if (partitionNames.isTemp()) {
throw new AnalysisException("Do not support deleting temp partitions");
}
}

if (wherePredicate == null) {
throw new AnalysisException("Where clause is not set");
}

// analyze predicate
analyzePredicate(wherePredicate);
}

private void analyzePredicate(Expr predicate) throws AnalysisException {
if (predicate instanceof BinaryPredicate) {
BinaryPredicate binaryPredicate = (BinaryPredicate) predicate;
Expr leftExpr = binaryPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new AnalysisException("Left expr of binary predicate should be column name");
}
Expr rightExpr = binaryPredicate.getChild(1);
if (!(rightExpr instanceof LiteralExpr)) {
throw new AnalysisException("Right expr of binary predicate should be value");
}
deleteConditions.add(binaryPredicate);
} else if (predicate instanceof CompoundPredicate) {
CompoundPredicate compoundPredicate = (CompoundPredicate) predicate;
if (compoundPredicate.getOp() != Operator.AND) {
throw new AnalysisException("Compound predicate's op should be AND");
}

analyzePredicate(compoundPredicate.getChild(0));
analyzePredicate(compoundPredicate.getChild(1));
} else if (predicate instanceof IsNullPredicate) {
IsNullPredicate isNullPredicate = (IsNullPredicate) predicate;
Expr leftExpr = isNullPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new AnalysisException("Left expr of is_null predicate should be column name");
}
deleteConditions.add(isNullPredicate);
} else if (predicate instanceof InPredicate) {
InPredicate inPredicate = (InPredicate) predicate;
Expr leftExpr = inPredicate.getChild(0);
if (!(leftExpr instanceof SlotRef)) {
throw new AnalysisException("Left expr of binary predicate should be column name");
}
int inElementNum = inPredicate.getInElementNum();
int maxAllowedInElementNumOfDelete = Config.max_allowed_in_element_num_of_delete;
if (inElementNum > maxAllowedInElementNumOfDelete) {
throw new AnalysisException("Element num of predicate should not be more than " +
maxAllowedInElementNumOfDelete);
}
for (int i = 1; i <= inElementNum; i++) {
Expr expr = inPredicate.getChild(i);
if (!(expr instanceof LiteralExpr)) {
throw new AnalysisException("Child of in predicate should be value");
}
}
deleteConditions.add(inPredicate);
} else {
throw new AnalysisException("Where clause only supports compound predicate, binary predicate, " +
"is_null predicate and in predicate");
}
public void setDeleteConditions(List<Predicate> deleteConditions) {
this.deleteConditions = deleteConditions;
}

@Override
public String toSql() {
StringBuilder sb = new StringBuilder();
sb.append("DELETE FROM ").append(tblName.toSql());
if (partitionNames != null) {
sb.append(" PARTITION (");
sb.append(Joiner.on(", ").join(partitionNames.getPartitionNames()));
sb.append(")");
}
sb.append(" WHERE ").append(wherePredicate.toSql());
return sb.toString();
}

public boolean supportNewPlanner() {
// table must present if analyzed by new analyzer
return table != null;
public boolean shouldHandledByDeleteHandler() {
// table must present if analyzed and is a delele for primary key table, so it is executed by new
// planner&execution engine, otherwise(non-pk table) it is handled by DeleteHandler
return table == null;
}

public void setTable(Table table) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1341,8 +1341,9 @@ public ParseNode visitDeleteStatement(StarRocksParser.DeleteStatementContext con
if (context.partitionNames() != null) {
partitionNames = (PartitionNames) visit(context.partitionNames());
}
List<Relation> usingRelations = context.using != null ? visit(context.using.relation(), Relation.class) : null;
Expr where = context.where != null ? (Expr) visit(context.where) : null;
DeleteStmt ret = new DeleteStmt(targetTableName, partitionNames, where);
DeleteStmt ret = new DeleteStmt(targetTableName, partitionNames, usingRelations, where);
if (context.explainDesc() != null) {
ret.setIsExplain(true, getExplainType(context.explainDesc()));
}
Expand Down
Loading

0 comments on commit ca4e903

Please sign in to comment.