Skip to content
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

Feature/50 support merge statement #53

Merged
merged 20 commits into from
Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ced30fb
#46: added group by and having clauses
AnastasiiaSergienko May 22, 2019
1b4c8ca
#46: added order by clause
AnastasiiaSergienko May 23, 2019
592dab2
#50: Fixed warnings about unspecified generic types.
redcatbear Nov 14, 2019
154fb08
#50: Support MERGE Statement (incomplete).
redcatbear Nov 15, 2019
0b76051
#50: Support `MERGE` statements
redcatbear Nov 19, 2019
c5970ed
#50: Improved documentation.
redcatbear Nov 19, 2019
12f91dc
#50: Improved `README.md`.
redcatbear Nov 20, 2019
1859a63
#50: Added "in a nutshell" section.
redcatbear Nov 20, 2019
cedfbaf
Update src/main/java/com/exasol/sql/ddl/create/CreateTable.java
redcatbear Nov 20, 2019
04a2a47
#50: Worked in review findings of @AnstasiiaSergienko.
redcatbear Nov 20, 2019
7d294ae
Apply suggestions from code review
redcatbear Nov 20, 2019
348d8f5
Merge branch 'feature/50-Support_MERGE_Statement' of https://github.c…
redcatbear Nov 20, 2019
afbfc2c
#50: Fixed broken link.
redcatbear Nov 20, 2019
c0f32f3
Merge branch 'master' into feature/50-Support_MERGE_Statement
redcatbear Nov 20, 2019
bfd5758
#50: Support `WHERE` in `MERGE` strategies.
redcatbear Nov 26, 2019
9f239ea
Merge branch 'master' into feature/50-Support_MERGE_Statement
redcatbear Nov 26, 2019
02fb0f4
#50: Resolved merge conflicts.
redcatbear Nov 26, 2019
4c498c1
#50: Fixed broken JavaDoc.
redcatbear Nov 26, 2019
4a48199
#50: Fixed review findings of @morazow.
redcatbear Nov 26, 2019
08f41e7
Update src/main/java/com/exasol/sql/dml/merge/MergeMethodDefinition.java
redcatbear Nov 26, 2019
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
14 changes: 14 additions & 0 deletions doc/user_guide/statements/merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ merge.whenNotMatched()

Check the JavaDoc of class `Merge` for a more information.

### Using a Filter on a `MERGE` Strategy

All three merge strategies can be narrowed down with a `WHERE` clause, to limit the dataset they work on.

Here is an example for a `MERGE` statement with a `DELETE` strategy.

```java
merge.using("src") //
.on(eq(column("src", "c1"), column("dst", "c1"))) //
.whenMatched() //
.thenDelete() //
.where(gt(column("src", "c5"), integerLiteral(1000)));
```

### Rendering `MERGE` Statements

Use the `MergeRenderer` to render `Merge` objects into SQL strings.
Expand Down
18 changes: 15 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exasol</groupId>
<artifactId>sql-statement-builder</artifactId>
<version>2.0.0</version>
<version>3.0.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 Expand Up @@ -100,6 +101,17 @@
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.24.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
Expand Down Expand Up @@ -169,7 +181,7 @@
</executions>
<configuration>
<charset>UTF-8</charset>
<doclint/>
<doclint />
<serialwarn>true</serialwarn>
<failOnError>true</failOnError>
<failOnWarnings>true</failOnWarnings>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public abstract class AbstractInsertValueTable<T extends AbstractInsertValueTabl

/**
* Create the abstract base for a fragment containing a value table.
*
*
* @param root root fragment
*/
public AbstractInsertValueTable(final Fragment root) {
Expand All @@ -23,7 +23,7 @@ public AbstractInsertValueTable(final Fragment root) {
protected abstract T self();

protected synchronized void createInsertValueInstanceIfItDoesNotExist() {
if (this.insertValueTable == null) {
if (!hasValues()) {
this.insertValueTable = new ValueTable(this);
}
}
Expand All @@ -35,7 +35,7 @@ protected synchronized void createInsertValueInstanceIfItDoesNotExist() {
* @return <code>this</code> for fluent programming
*/
public synchronized T field(final String... names) {
if (this.insertFields == null) {
if (!hasFields()) {
this.insertFields = new InsertFields(this.getRoot());
}
this.insertFields.add(names);
Expand All @@ -49,7 +49,7 @@ public synchronized T field(final String... names) {
* @return <code>this</code> for fluent programming
*/
public synchronized T valueTable(final ValueTable table) {
if (this.insertValueTable != null) {
if (hasValues()) {
throw new IllegalStateException("Cannot add a value table to an INSERT command that already has one.");
}
this.insertValueTable = table;
Expand Down Expand Up @@ -108,4 +108,22 @@ public synchronized T valuePlaceholders(final int amount) {
}
return self();
}
}

/**
* Check if a value table is present.
*
* @return {@code true} if a value table exists
*/
public boolean hasValues() {
return this.insertValueTable != null;
}

/**
* Check if a insert fields are defined.
*
* @return {@code true} if insert fields are defined
*/
public boolean hasFields() {
return this.insertFields != null;
}
}
73 changes: 54 additions & 19 deletions src/main/java/com/exasol/sql/dml/merge/Merge.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class Merge extends AbstractFragment implements SqlStatement, MergeFragme
private final Table destinationTable;
private UsingClause using;
private OnClause on;
private BooleanExpression condition;
private MatchedClause matched;
private NotMatchedClause notMatched;

Expand Down Expand Up @@ -60,6 +59,24 @@ public Merge using(final String sourceTable, final String as) {
return this;
}

/**
* Get the {@code USING} clause of the {@code MERGE} statement.
*
* @return destination table
*/
public UsingClause getUsing() {
return this.using;
}

/**
* Check if the {@code USING} clause exists.
*
* @return {@code true} if the {@code USING} clause exists.
*/
protected boolean hasUsing() {
return this.using != null;
}

/**
* Define the merge criteria.
*
Expand All @@ -71,42 +88,60 @@ public Merge on(final BooleanExpression condition) {
return this;
}

/**
* Get the merge criteria (i.e. the `ON` clause).
*
* @return criteria that must be met for the rows in source and destination to be considered a match.
*/
public OnClause getOn() {
return this.on;
}

/**
* Check if the {@code ON} clause exists.
*
* @return {@code true} if the {@code ON} clause exists.
*/
protected boolean hasOn() {
return this.on != null;
redcatbear marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Define the merge strategy if the match criteria is met.
*
* @return match strategy
*/
public MatchedClause whenMatched() {
this.matched = new MatchedClause(this.root);
this.matched = new MatchedClause(this);
return this.matched;
}

/**
* Define the merge strategy if the match criteria is not met.
* Check if the {@code WHEN MATCHED} clause exists.
*
* @return not matched strategy
* @return {@code true} if the {@code WHEN MATCHED} clause exists
*/
public NotMatchedClause whenNotMatched() {
this.notMatched = new NotMatchedClause(this.root);
return this.notMatched;
protected boolean hasMatched() {
return this.matched != null;
}

/**
* Get the {@code USING} clause of the {@code MERGE} statement.
* Define the merge strategy if the match criteria is not met.
*
* @return destination table
* @return not matched strategy
*/
public UsingClause getUsing() {
return this.using;
public NotMatchedClause whenNotMatched() {
this.notMatched = new NotMatchedClause(this);
return this.notMatched;
}

/**
* Get the merge condition.
* Check if the {@code WHEN NOT MATCHED} clause exists.
*
* @return criteria that must be met for the rows in source and destination to be considered a match.
* @return true if the {@code WHEN NOT MATCHED} clause exists
*/
public BooleanExpression getCondition() {
return this.condition;
protected boolean hasNotMatched() {
return this.notMatched != null;
}

@Override
Expand All @@ -115,16 +150,16 @@ public void accept(final MergeVisitor visitor) {
if (this.destinationTable != null) {
this.destinationTable.accept(visitor);
}
if (this.using != null) {
if (hasUsing()) {
this.using.accept(visitor);
}
if (this.on != null) {
if (hasOn()) {
this.on.accept(visitor);
}
if (this.matched != null) {
if (hasMatched()) {
this.matched.accept(visitor);
}
if (this.notMatched != null) {
if (hasNotMatched()) {
this.notMatched.accept(visitor);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/com/exasol/sql/dml/merge/MergeDeleteClause.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.exasol.sql.dml.merge;

import com.exasol.sql.AbstractFragment;
import com.exasol.sql.Fragment;

/**
* This class represents the {@code MERGE} strategy of deleting matched rows.
*/
public class MergeDeleteClause extends AbstractFragment implements MergeFragment {
public class MergeDeleteClause extends MergeMethodDefinition implements MergeFragment {
/**
* Create a new instance of a {@link MergeDeleteClause}.
*
Expand All @@ -19,5 +18,8 @@ public MergeDeleteClause(final Fragment root) {
@Override
public void accept(final MergeVisitor visitor) {
visitor.visit(this);
if (hasWhere()) {
this.where.accept(visitor);
}
}
}
41 changes: 39 additions & 2 deletions src/main/java/com/exasol/sql/dml/merge/MergeInsertClause.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import com.exasol.sql.Fragment;
import com.exasol.sql.dml.insert.AbstractInsertValueTable;
import com.exasol.sql.dql.select.WhereClause;
import com.exasol.sql.expression.BooleanExpression;

/**
* Represents the {@code MERGE} strategy of inserting rows from the source that do not match any row in the destination.
*/
public class MergeInsertClause extends AbstractInsertValueTable<MergeInsertClause> implements MergeFragment {
protected WhereClause where = null;

/**
* Create a new instance of a {@link MergeInsertClause}.
*
Expand All @@ -21,14 +25,47 @@ protected MergeInsertClause self() {
return this;
}

/**
* Add a {@code WHERE} clause insertion definition.
*
* @param expression filter expression
* @return parent {@code MERGE} statement
*/
public Merge where(final BooleanExpression expression) {
final Merge merge = (Merge) this.getRoot();
this.where = new WhereClause(merge, expression);
return merge;
}

/**
* Get the {@code WHERE} clause of the insert definition.
*
* @return {@code WHERE} clause
*/
public WhereClause getWhere() {
return this.where;
}

/**
* Check if the {@code WHERE} clause exists.
*
* @return {@code true} if the {@code WHERE} clause exists
*/
public boolean hasWhere() {
return this.where != null;
}

@Override
public void accept(final MergeVisitor visitor) {
visitor.visit(this);
if (this.insertFields != null) {
if (hasFields()) {
this.insertFields.accept(visitor);
}
if (this.insertValueTable != null) {
if (hasValues()) {
this.insertValueTable.accept(visitor);
}
if (hasWhere()) {
this.where.accept(visitor);
}
}
}
52 changes: 52 additions & 0 deletions src/main/java/com/exasol/sql/dml/merge/MergeMethodDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.exasol.sql.dml.merge;

import com.exasol.sql.AbstractFragment;
import com.exasol.sql.Fragment;
import com.exasol.sql.dql.select.WhereClause;
import com.exasol.sql.expression.BooleanExpression;

/**
* Abstract base class for merge method definitions like {@code WHEN MATCHED THEN UPDATE}.
*/
public abstract class MergeMethodDefinition extends AbstractFragment {
protected WhereClause where = null;

/**
* Create the abstract base for a merge method definition.
*
* @param root root {@code MERGE} statement
*/
public MergeMethodDefinition(final Fragment root) {
super(root);
}

/**
* Add a {@code WHERE} clause {@code MERGE} definition.
*
* @param expression filter expression
* @return parent {@code MERGE} statement
*/
public Merge where(final BooleanExpression expression) {
final Merge merge = (Merge) this.getRoot();
this.where = new WhereClause(merge, expression);
return merge;
}

/**
* Get the {@code WHERE} clause of the merge method definition.
*
* @return {@code WHERE} clause
*/
public WhereClause getWhere() {
return this.where;
}

/**
* Check if the {@code WHERE} clause exists.
*
* @return {@code true} if the {@code WHERE} clause exists
*/
public boolean hasWhere() {
return this.where != null;
}
}
Loading