Skip to content

Commit

Permalink
#54: Improved MERGE and its documentation. (#55)
Browse files Browse the repository at this point in the history
* #54: Improved `MERGE` and its documentation.
  • Loading branch information
redcatbear authored Nov 27, 2019
1 parent 5c263c4 commit 036c532
Show file tree
Hide file tree
Showing 15 changed files with 270 additions and 50 deletions.
13 changes: 10 additions & 3 deletions doc/user_guide/statements/merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ You can construct [`MERGE`](https://docs.exasol.com/sql/merge.htm) SQL statement
`Merge` supports a combination of `INSERT`, `UPDATE` and `DELETE` where source data is merged into a destination table. The merge strategy is configurable and depends on whether or not a row in source and destination are considered a match.
Of course the criteria for that match is configurable too.

Note that while the individual merge strategies are optional parts of the `MERGE` statement, you need to pick *at least one* to get a valid statement.

## Creating `MERGE` Commands

You can create a minimalistic `MERGE` like this:
You can create a basic `MERGE` like this:

```java
final Merge merge = StatementFactory.getInstance()
Expand All @@ -16,7 +18,9 @@ final Merge merge = StatementFactory.getInstance()
.on(eq(column("source", "id"), column("destination", "id");
```

### Fine Tuning the `MERGE` Strategies
As mentioned before, this statement is not complete without selecting at least one [merge strategy](#merge-strategies).

### `MERGE` Strategies

If you need more control over what happens in case of matching rows, you can add `whenMatched()`.

Expand All @@ -29,9 +33,12 @@ merge.whenMatched()
.thenUpdate() //
.setToDefault("c2") //
.set("c3", "foo") //
.set("c4", 42);
.set("c4", 42) //
.set("c5", integerLiteral(9000));
```

The basic `set()` method expects a column and a value expression. For added convenience the set methods are overloaded to allow using literals directly.

And another example for `thenDelete()`.

```java
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.exasol</groupId>
<artifactId>sql-statement-builder</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<name>Exasol SQL Statement Builder</name>
<description>This module provides a Builder for SQL statements that helps creating the correct structure and validates variable parts of the statements.</description>
<url>https://github.com/exasol/sql-statement-builder</url>
Expand Down
40 changes: 38 additions & 2 deletions src/main/java/com/exasol/sql/dml/merge/MatchedClause.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ public MergeUpdateClause thenUpdate() {
return this.mergeUpdateClause;
}

/**
* Check if the {@code THEN UPDATE} clause is present.
*
* @return {@code true} if the update clause is present
*/
public boolean hasUpdate() {
return this.mergeUpdateClause != null;
}

/**
* Get the {@code THEN UPDATE} clause.
*
* @return {@code THEN UPDATE} clause
*/
public MergeUpdateClause getUpdate() {
return this.mergeUpdateClause;
}

/**
* Select deleting as merge strategy for rows where that are considered matches between source and destination.
*
Expand All @@ -42,13 +60,31 @@ public MergeDeleteClause thenDelete() {
return this.mergeDeleteClause;
}

/**
* Check if the {@code THEN DELETE} clause is present.
*
* @return {@code true} if the delete clause is present.
*/
public boolean hasDelete() {
return this.mergeDeleteClause != null;
}

/**
* Get the {@code THEN DELETE} clause.
*
* @return {@code THEN DELETE} clause
*/
public MergeDeleteClause getDelete() {
return this.mergeDeleteClause;
}

@Override
public void accept(final MergeVisitor visitor) {
visitor.visit(this);
if (this.mergeUpdateClause != null) {
if (hasUpdate()) {
this.mergeUpdateClause.accept(visitor);
}
if (this.mergeDeleteClause != null) {
if (hasDelete()) {
this.mergeDeleteClause.accept(visitor);
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/exasol/sql/dml/merge/Merge.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ protected boolean hasMatched() {
return this.matched != null;
}

/**
* Get the {@code WHEN MATCHED} clause.
*
* @return {@code WHEN MATCHED} clause
*/
public MatchedClause getMatched() {
return this.matched;
}

/**
* Define the merge strategy if the match criteria is not met.
*
Expand All @@ -144,6 +153,15 @@ protected boolean hasNotMatched() {
return this.notMatched != null;
}

/**
* Get the {@code WHEN NOT MATCHED} clause.
*
* @return {@code WHEN NOT MATCHED} clause
*/
public MatchedClause getNotMatched() {
return this.matched;
}

@Override
public void accept(final MergeVisitor visitor) {
visitor.visit(this);
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/com/exasol/sql/dml/merge/MergeUpdateClause.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,26 @@ public MergeUpdateClause(final Fragment root) {
}

/**
* Update a column with a string value.
* Update a column with a value expression.
*
* @param column column to be updated
* @param literal string literal
* @param expression value expression
* @return {@code this} for fluent programming
*/
public MergeUpdateClause set(final String column, final String literal) {
addColumnUpdate(column, StringLiteral.of(literal));
public MergeUpdateClause set(final String column, final ValueExpression expression) {
this.columnUpdates.add(new MergeColumnUpdate(this.root, column, expression));
return this;
}

protected void addColumnUpdate(final String column, final ValueExpression expression) {
this.columnUpdates.add(new MergeColumnUpdate(this.root, column, expression));
/**
* Update a column with a string value.
*
* @param column column to be updated
* @param literal string literal
* @return {@code this} for fluent programming
*/
public MergeUpdateClause set(final String column, final String literal) {
return set(column, StringLiteral.of(literal));
}

/**
Expand All @@ -45,8 +52,7 @@ protected void addColumnUpdate(final String column, final ValueExpression expres
* @return {@code this} for fluent programming
*/
public MergeUpdateClause set(final String column, final int literal) {
addColumnUpdate(column, IntegerLiteral.of(literal));
return this;
return set(column, IntegerLiteral.of(literal));
}

/**
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/com/exasol/sql/dml/merge/NotMatchedClause.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,28 @@ public MergeInsertClause thenInsert() {
return this.mergeInsertClause;
}

/**
* Check if the {@code THEN INSERT} clause is present.
*
* @return {@code true} if the {@code THEN INSERT} clause is present
*/
public boolean hasInsert() {
return this.mergeInsertClause != null;
}

/**
* Get the {@code THEN INSERT} clause.
*
* @return {@code THEN INSERT} clause
*/
public MergeInsertClause getInsert() {
return this.mergeInsertClause;
}

@Override
public void accept(final MergeVisitor visitor) {
visitor.visit(this);
if (this.mergeInsertClause != null) {
if (hasInsert()) {
this.mergeInsertClause.accept(visitor);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/exasol/sql/expression/ExpressionTerm.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ public static ColumnReference column(final String column) {

/**
* Create a reference to a column in a specific table.
*
* @param column column name
* @param table table name
* @param column column name
*
* @return column reference
*/
public static ColumnReference column(final String column, final String table) {
public static ColumnReference column(final String table, final String column) {
return ColumnReference.column(table, column);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ public interface ValueExpression extends TreeNode {
* @param visitor visitor to accept
*/
public void accept(ValueExpressionVisitor visitor);
}
}
51 changes: 51 additions & 0 deletions src/test/java/com/exasol/sql/dml/merge/MatchedClauseTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.exasol.sql.dml.merge;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.sameInstance;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class MatchedClauseTest {
private MatchedClause matched;

@BeforeEach
void beforeEach() {
this.matched = new MatchedClause(null);
}

@Test
void testHasUpdateFalseByDefault() {
assertThat(this.matched.hasUpdate(), equalTo(false));
}

@Test
void testHasUpdateTrue() {
this.matched.thenUpdate();
assertThat(this.matched.hasUpdate(), equalTo(true));
}

@Test
void testGetUpdate() {
final MergeUpdateClause update = this.matched.thenUpdate();
assertThat(this.matched.getUpdate(), sameInstance(update));
}

@Test
void testHasDeleteFalseByDefault() {
assertThat(this.matched.hasDelete(), equalTo(false));
}

@Test
void testHasDeleteTrue() {
this.matched.thenDelete();
assertThat(this.matched.hasDelete(), equalTo(true));
}

@Test
void testGetDelete() {
final MergeDeleteClause delete = this.matched.thenDelete();
assertThat(this.matched.getDelete(), sameInstance(delete));
}
}
38 changes: 38 additions & 0 deletions src/test/java/com/exasol/sql/dml/merge/MergeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.exasol.sql.dml.merge;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class MergeTest {
private Merge merge;

@BeforeEach
void beforeEach() {
this.merge = new Merge("dst").using("src");
}

@Test
void testHasMatchedFalseByDefault() {
assertThat(this.merge.hasMatched(), equalTo(false));
}

@Test
void testHasMatchedTrue() {
this.merge.whenMatched();
assertThat(this.merge.hasMatched(), equalTo(true));
}

@Test
void testHasNotMatchedFalseByDefault() {
assertThat(this.merge.hasNotMatched(), equalTo(false));
}

@Test
void testHasNotMatchedTrue() {
this.merge.whenNotMatched();
assertThat(this.merge.hasNotMatched(), equalTo(true));
}
}
34 changes: 34 additions & 0 deletions src/test/java/com/exasol/sql/dml/merge/NotMatchedClauseTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.exasol.sql.dml.merge;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.sameInstance;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class NotMatchedClauseTest {
private NotMatchedClause notMatched;

@BeforeEach
void beforeEach() {
this.notMatched = new NotMatchedClause(null);
}

@Test
void testHasInsertFalseByDefault() {
assertThat(this.notMatched.hasInsert(), equalTo(false));
}

@Test
void testHasInsertTrue() {
this.notMatched.thenInsert();
assertThat(this.notMatched.hasInsert(), equalTo(true));
}

@Test
void testGetInsert() {
final MergeInsertClause insert = this.notMatched.thenInsert();
assertThat(this.notMatched.getInsert(), sameInstance(insert));
}
}
Loading

0 comments on commit 036c532

Please sign in to comment.