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

Add support for Postgres INSERT ON CONFLICT update | nothing #3324

Merged
merged 2 commits into from
Feb 9, 2024
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
104 changes: 104 additions & 0 deletions ebean-api/src/main/java/io/ebean/DInsertOptionsBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.ebean;

final class DInsertOptionsBuilder implements InsertOptions.Builder {

private Boolean getGeneratedKeys;
private boolean onConflictUpdate;
private boolean onConflictNothing;
private String constraint;
private String uniqueColumns;
private String updateSet;

@Override
public InsertOptions.Builder onConflictNothing() {
this.onConflictNothing = true;
return this;
}

@Override
public InsertOptions.Builder onConflictUpdate() {
this.onConflictUpdate = true;
return this;
}

@Override
public InsertOptions.Builder constraint(String constraint) {
this.constraint = constraint;
return this;
}

@Override
public InsertOptions.Builder uniqueColumns(String uniqueColumns) {
this.uniqueColumns = uniqueColumns;
return this;
}

@Override
public InsertOptions.Builder updateSet(String updateSet) {
this.updateSet = updateSet;
return this;
}

@Override
public InsertOptions.Builder getGeneratedKeys(boolean getGeneratedKeys) {
this.getGeneratedKeys = getGeneratedKeys;
return this;
}

@Override
public InsertOptions build() {
return new Options(constraint, uniqueColumns, updateSet, onConflictUpdate, onConflictNothing, getGeneratedKeys);
}

static final class Options implements InsertOptions {

private static final String UPDATE = "U";
private static final String NOTHING = "N";
private static final String NORMAL = "_";
private final String key;
private final Boolean getGeneratedKeys;
private final String constraint;
private final String uniqueColumns;
private final String updateSet;

Options(String constraint, String uniqueColumns, String updateSet, boolean onConflictUpdate, boolean onConflictNothing, Boolean getGeneratedKeys) {
this.constraint = constraint;
this.uniqueColumns = uniqueColumns;
this.updateSet = updateSet;
this.getGeneratedKeys = getGeneratedKeys;
this.key = (onConflictUpdate ? UPDATE : onConflictNothing ? NOTHING : NORMAL)
+ '+' + plus(constraint)
+ '+' + plus(uniqueColumns)
+ '+' + plus(updateSet);
}

private String plus(String val) {
return val == null ? "" : val;
}

@Override
public String key() {
return key;
}

@Override
public String constraint() {
return constraint;
}

@Override
public String uniqueColumns() {
return uniqueColumns;
}

@Override
public String updateSet() {
return updateSet;
}

@Override
public Boolean getGetGeneratedKeys() {
return getGeneratedKeys;
}
}
}
31 changes: 31 additions & 0 deletions ebean-api/src/main/java/io/ebean/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -1227,22 +1227,53 @@ static DatabaseBuilder builder() {
*/
void insert(Object bean);

/**
* Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING).
* <p>
* Currently, this is limited to use with Postgres only,
* <p>
* When using this ebean will look to determine the unique columns by looking at
* the mapping like {@code @Column(unique=true} and {@code @Index(unique=true}.
*/
void insert(Object bean, InsertOptions insertOptions);

/**
* Insert the bean with a transaction.
*/
void insert(Object bean, Transaction transaction);

/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insert(Object bean, InsertOptions insertOptions, Transaction transaction);

/**
* Insert a collection of beans. If there is no current transaction one is created and used to
* insert all the beans in the collection.
*/
void insertAll(Collection<?> beans);

/**
* Insert the beans with options - typically ON CONFLICT DO UPDATE | DO NOTHING.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options);

/**
* Insert a collection of beans with an explicit transaction.
*/
void insertAll(Collection<?> beans, Transaction transaction);

/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction);

/**
* Execute explicitly passing a transaction.
*/
Expand Down
119 changes: 119 additions & 0 deletions ebean-api/src/main/java/io/ebean/InsertOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.ebean;

import io.avaje.lang.Nullable;

/**
* Options to be used with insert such as ON CONFLICT DO UPDATE | NOTHING.
*/
public interface InsertOptions {

/**
* Use ON CONFLICT UPDATE with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_UPDATE = InsertOptions.builder()
.onConflictUpdate()
.build();

/**
* Use ON CONFLICT DO NOTHING with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_NOTHING = InsertOptions.builder()
.onConflictNothing()
.build();

/**
* Return a builder for InsertOptions.
*/
static Builder builder() {
return new DInsertOptionsBuilder();
}

/**
* Return the constraint name that is used for ON CONFLICT.
*/
@Nullable
String constraint();

/**
* Return the unique columns that is used for ON CONFLICT.
* <p>
* When not explicitly set will use mapping like {@code @Column(unique=true)} to determine the
* non-unique columns.
*/
@Nullable
String uniqueColumns();

/**
* Return the ON CONFLICT UPDATE SET clause.
* <p>
* When not set will use the non-unique columns.
*/
@Nullable
String updateSet();

/**
* Return if GetGeneratedKeys should be used to fetch the generated keys after insert.
*/
@Nullable
Boolean getGetGeneratedKeys();

/**
* Return the key for these build options.
*/
String key();

/**
* The builder for InsertOptions.
*/
interface Builder {

/**
* Use a ON CONFLICT UPDATE automatically determining the unique columns.
*/
Builder onConflictUpdate();

/**
* Use a ON CONFLICT DO NOTHING automatically determining the unique columns.
*/
Builder onConflictNothing();

/**
* Specify an explicit conflict constraint name.
* <p>
* When this is used then unique columns will not be used.
*/
Builder constraint(String constraint);

/**
* Specify the unique columns for the conflict target.
* <p>
* When not specified and constraint is also not specified then
* it will automatically determine the unique columns
* based on mapping like {@code @Column(unique=true)} and
* {@code @Index(unique=true)} .
*/
Builder uniqueColumns(String uniqueColumns);

/**
* Specify the ON CONFLICT DO UPDATE SET clause.
* <p>
* When not specified ebean will include all the non-unique columns.
*/
Builder updateSet(String updateSet);

/**
* Specify if GetGeneratedKeys should be used to return generated keys.
*/
Builder getGeneratedKeys(boolean getGeneratedKeys);

/**
* Build and return the insert options.
*/
InsertOptions build();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1651,41 +1651,50 @@ public void updateAll(@Nullable Collection<?> beans, @Nullable Transaction trans
}, transaction);
}

/**
* Insert the bean.
*/
@Override
public void insert(Object bean) {
insert(bean, null);
persister.insert(checkEntityBean(bean), null, null);
}

@Override
public void insert(Object bean, @Nullable InsertOptions insertOptions) {
persister.insert(checkEntityBean(bean), insertOptions, null);
}

/**
* Insert the bean with a transaction.
*/
@Override
public void insert(Object bean, @Nullable Transaction transaction) {
persister.insert(checkEntityBean(bean), transaction);
persister.insert(checkEntityBean(bean), null, transaction);
}

@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
persister.insert(checkEntityBean(bean), insertOptions, transaction);
}

/**
* Insert all beans in the collection.
*/
@Override
public void insertAll(Collection<?> beans) {
insertAll(beans, null);
insertAll(beans, null, null);
}

@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
insertAll(beans, options, null);
}

/**
* Insert all beans in the collection with a transaction.
*/
@Override
public void insertAll(@Nullable Collection<?> beans, @Nullable Transaction transaction) {
insertAll(beans, null, transaction);
}

@Override
public void insertAll(@Nullable Collection<?> beans, InsertOptions options, @Nullable Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), txn);
persister.insert(checkEntityBean(bean), options, txn);
}
return 0;
}, transaction);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;

import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
Expand Down Expand Up @@ -124,6 +125,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Many-to-many intersection table changes that are held for later batch processing.
*/
private List<SaveMany> saveMany;
private InsertOptions insertOptions;

public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
Expand Down Expand Up @@ -1408,4 +1410,12 @@ public void setSaveRecurse() {
private void setGeneratedId() {
beanDescriptor.setGeneratedId(entityBean, transaction);
}

public void setInsertOptions(InsertOptions insertOptions) {
this.insertOptions = insertOptions;
}

public InsertOptions insertOptions() {
return insertOptions;
}
}
Loading
Loading