Skip to content

feat: support any number/order of merge operations #1938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
63 changes: 51 additions & 12 deletions src/main/java/net/sf/jsqlparser/statement/merge/Merge.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class Merge implements Statement {

Expand All @@ -37,9 +38,43 @@ public class Merge implements Statement {
private MergeInsert mergeInsert;
private MergeUpdate mergeUpdate;
private boolean insertFirst = false;
private List<MergeOperation> operations;

private OutputClause outputClause;

private void deriveOperationsFromStandardClauses() {
List<MergeOperation> operations = new ArrayList<>();
if (insertFirst) {
Optional.ofNullable(mergeInsert).ifPresent(operations::add);
Optional.ofNullable(mergeUpdate).ifPresent(operations::add);
} else {
Optional.ofNullable(mergeUpdate).ifPresent(operations::add);
Optional.ofNullable(mergeInsert).ifPresent(operations::add);
}
this.operations = operations;
}

private void deriveStandardClausesFromOperations() {
List<MergeOperation> applicableOperations =
Optional.ofNullable(operations).orElse(Collections.emptyList()).stream()
.filter(o -> o instanceof MergeUpdate || o instanceof MergeInsert)
.collect(Collectors.toList());
mergeUpdate = applicableOperations.stream()
.filter(o -> o instanceof MergeUpdate)
.map(MergeUpdate.class::cast)
.findFirst()
.orElse(null);
mergeInsert = applicableOperations.stream()
.filter(o -> o instanceof MergeInsert)
.map(MergeInsert.class::cast)
.findFirst()
.orElse(null);
insertFirst = applicableOperations.stream()
.findFirst()
.map(o -> o instanceof MergeInsert)
.orElse(false);
}

public List<WithItem> getWithItemsList() {
return withItemsList;
}
Expand Down Expand Up @@ -129,12 +164,22 @@ public void setOnCondition(Expression onCondition) {
this.onCondition = onCondition;
}

public List<MergeOperation> getOperations() {
return operations;
}

public void setOperations(List<MergeOperation> operations) {
this.operations = operations;
deriveStandardClausesFromOperations();
}

public MergeInsert getMergeInsert() {
return mergeInsert;
}

public void setMergeInsert(MergeInsert insert) {
this.mergeInsert = insert;
public void setMergeInsert(MergeInsert mergeInsert) {
this.mergeInsert = mergeInsert;
deriveOperationsFromStandardClauses();
}

public MergeUpdate getMergeUpdate() {
Expand All @@ -143,6 +188,7 @@ public MergeUpdate getMergeUpdate() {

public void setMergeUpdate(MergeUpdate mergeUpdate) {
this.mergeUpdate = mergeUpdate;
deriveOperationsFromStandardClauses();
}

@Override
Expand All @@ -156,6 +202,7 @@ public boolean isInsertFirst() {

public void setInsertFirst(boolean insertFirst) {
this.insertFirst = insertFirst;
deriveOperationsFromStandardClauses();
}

public OutputClause getOutputClause() {
Expand Down Expand Up @@ -193,16 +240,8 @@ public String toString() {
b.append(" ON ");
b.append(onCondition);

if (insertFirst && mergeInsert != null) {
b.append(mergeInsert);
}

if (mergeUpdate != null) {
b.append(mergeUpdate);
}

if (!insertFirst && mergeInsert != null) {
b.append(mergeInsert);
if (operations != null && !operations.isEmpty()) {
operations.forEach(b::append);
}

if (outputClause != null) {
Expand Down
47 changes: 47 additions & 0 deletions src/main/java/net/sf/jsqlparser/statement/merge/MergeDelete.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2024 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.merge;

import net.sf.jsqlparser.expression.Expression;

import java.io.Serializable;

public class MergeDelete implements Serializable, MergeOperation {
private Expression andPredicate;

public Expression getAndPredicate() {
return andPredicate;
}

public void setAndPredicate(Expression andPredicate) {
this.andPredicate = andPredicate;
}

public MergeDelete withAndPredicate(Expression andPredicate) {
this.setAndPredicate(andPredicate);
return this;
}

@Override
public void accept(MergeOperationVisitor mergeOperationVisitor) {
mergeOperationVisitor.visit(this);
}

@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(" WHEN MATCHED");
if (andPredicate != null) {
b.append(" AND ").append(andPredicate.toString());
}
b.append(" THEN DELETE");
return b.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import java.util.Collection;
import java.util.Optional;

public class MergeInsert implements Serializable {
public class MergeInsert implements Serializable, MergeOperation {

private Expression andPredicate;
private ExpressionList<Column> columns;
Expand Down Expand Up @@ -57,6 +57,11 @@ public void setWhereCondition(Expression whereCondition) {
this.whereCondition = whereCondition;
}

@Override
public void accept(MergeOperationVisitor mergeOperationVisitor) {
mergeOperationVisitor.visit(this);
}

@Override
public String toString() {
StringBuilder b = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2024 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.merge;

/**
* Marker interface to cover {@link MergeDelete}, {@link MergeUpdate} and {@link MergeInsert}
*/
public interface MergeOperation {
void accept(MergeOperationVisitor mergeOperationVisitor);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2024 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.merge;

public interface MergeOperationVisitor {

void visit(MergeDelete mergeDelete);

void visit(MergeUpdate mergeUpdate);

void visit(MergeInsert mergeInsert);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2024 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.merge;

@SuppressWarnings({"PMD.UncommentedEmptyMethodBody"})
public class MergeOperationVisitorAdapter implements MergeOperationVisitor {
@Override
public void visit(MergeDelete mergeDelete) {

}

@Override
public void visit(MergeUpdate mergeUpdate) {

}

@Override
public void visit(MergeInsert mergeInsert) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.io.Serializable;
import java.util.List;

public class MergeUpdate implements Serializable {
public class MergeUpdate implements Serializable, MergeOperation {

private List<UpdateSet> updateSets;
private Expression andPredicate;
Expand Down Expand Up @@ -61,6 +61,11 @@ public void setDeleteWhereCondition(Expression deleteWhereCondition) {
this.deleteWhereCondition = deleteWhereCondition;
}

@Override
public void accept(MergeOperationVisitor mergeOperationVisitor) {
mergeOperationVisitor.visit(this);
}

@Override
public String toString() {
StringBuilder b = new StringBuilder();
Expand Down
118 changes: 118 additions & 0 deletions src/main/java/net/sf/jsqlparser/util/deparser/MergeDeParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2024 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.util.deparser;

import net.sf.jsqlparser.statement.merge.*;
import net.sf.jsqlparser.statement.select.WithItem;

import java.util.Iterator;
import java.util.List;

public class MergeDeParser extends AbstractDeParser<Merge> implements MergeOperationVisitor {
private final ExpressionDeParser expressionDeParser;

private final SelectDeParser selectDeParser;

public MergeDeParser(ExpressionDeParser expressionDeParser, SelectDeParser selectDeParser,
StringBuilder buffer) {
super(buffer);
this.expressionDeParser = expressionDeParser;
this.selectDeParser = selectDeParser;
}

@Override
void deParse(Merge merge) {
List<WithItem> withItemsList = merge.getWithItemsList();
if (withItemsList != null && !withItemsList.isEmpty()) {
buffer.append("WITH ");
for (Iterator<WithItem> iter = withItemsList.iterator(); iter.hasNext();) {
iter.next().accept(expressionDeParser);
if (iter.hasNext()) {
buffer.append(",");
}
buffer.append(" ");
}
}

buffer.append("MERGE ");
if (merge.getOracleHint() != null) {
buffer.append(merge.getOracleHint()).append(" ");
}
buffer.append("INTO ");
merge.getTable().accept(selectDeParser);

buffer.append(" USING ");
merge.getFromItem().accept(selectDeParser);

buffer.append(" ON ");
merge.getOnCondition().accept(expressionDeParser);

List<MergeOperation> operations = merge.getOperations();
if (operations != null && !operations.isEmpty()) {
operations.forEach(operation -> operation.accept(this));
}

if (merge.getOutputClause() != null) {
merge.getOutputClause().appendTo(buffer);
}
}

@Override
public void visit(MergeDelete mergeDelete) {
buffer.append(" WHEN MATCHED");
if (mergeDelete.getAndPredicate() != null) {
buffer.append(" AND ");
mergeDelete.getAndPredicate().accept(expressionDeParser);
}
buffer.append(" THEN DELETE");
}

@Override
public void visit(MergeUpdate mergeUpdate) {
buffer.append(" WHEN MATCHED");
if (mergeUpdate.getAndPredicate() != null) {
buffer.append(" AND ");
mergeUpdate.getAndPredicate().accept(expressionDeParser);
}
buffer.append(" THEN UPDATE SET ");
deparseUpdateSets(mergeUpdate.getUpdateSets(), buffer, expressionDeParser);

if (mergeUpdate.getWhereCondition() != null) {
buffer.append(" WHERE ");
mergeUpdate.getWhereCondition().accept(expressionDeParser);
}

if (mergeUpdate.getDeleteWhereCondition() != null) {
buffer.append(" DELETE WHERE ");
mergeUpdate.getDeleteWhereCondition().accept(expressionDeParser);
}
}

@Override
public void visit(MergeInsert mergeInsert) {
buffer.append(" WHEN NOT MATCHED");
if (mergeInsert.getAndPredicate() != null) {
buffer.append(" AND ");
mergeInsert.getAndPredicate().accept(expressionDeParser);
}
buffer.append(" THEN INSERT ");
if (mergeInsert.getColumns() != null) {
mergeInsert.getColumns().accept(expressionDeParser);
}
buffer.append(" VALUES ");
mergeInsert.getValues().accept(expressionDeParser);

if (mergeInsert.getWhereCondition() != null) {
buffer.append(" WHERE ");
mergeInsert.getWhereCondition().accept(expressionDeParser);
}
}

}
Loading