diff --git a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/DB2Engine.java b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/DB2Engine.java index 44636835..79149adf 100644 --- a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/DB2Engine.java +++ b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/DB2Engine.java @@ -37,7 +37,6 @@ import com.feedzai.commons.sql.abstraction.util.Constants; import com.feedzai.commons.sql.abstraction.util.PreparedStatementCapsule; import com.ibm.db2.jcc.am.SqlSyntaxErrorException; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import java.sql.Connection; @@ -426,92 +425,17 @@ protected MappedEntity createPreparedStatementForInserts(final DbEntity entity) logger.trace(insertStatement); logger.trace(insertReturnStatement); - PreparedStatement ps = null, psReturn = null, psWithAutoInc = null, psUpsert; + PreparedStatement ps, psReturn, psWithAutoInc; try { ps = conn.prepareStatement(insertStatement); psReturn = conn.prepareStatement(insertReturnStatement); psWithAutoInc = conn.prepareStatement(insertWithAutoInc); - final String upsert = buildUpsertStatement(entity, columns, values); - psUpsert = conn.prepareStatement(upsert); - - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - .setAutoIncColumn(returning) - .setUpsert(psUpsert); - - } catch (final IllegalArgumentException e) { - logger.trace("Returning entity without an UPSERT/MERGE prepared statement."); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - .setAutoIncColumn(returning); - + return new MappedEntity().setInsert(ps).setInsertReturning(psReturn).setInsertWithAutoInc(psWithAutoInc).setAutoIncColumn(returning); } catch (final SQLException ex) { throw new DatabaseEngineException("Something went wrong handling statement", ex); } - - } - - /** - * Helper method to create an upsert statement for this engine. - * - * @param entity The entity. - * @param columns The columns of this entity. - * @param values The values of the entity. - * - * @return An upsert statement. - */ - private String buildUpsertStatement(final DbEntity entity, final List columns, final List values) { - - if (entity.getPkFields().isEmpty() || columns.isEmpty() || values.isEmpty()) { - throw new IllegalArgumentException("The MERGE command was not created because the entity has no primary keys. Skipping statement creation."); - } - - List merge = new ArrayList<>(); - merge.add("MERGE INTO " + quotize(entity.getName()) + " AS dest"); - - merge.add("USING (VALUES(" + join(values, ", ") + ")) AS src (" + join(columns, ", ") + ")"); - - final List primaryKeys = entity.getPkFields() - .stream() - .map(com.feedzai.commons.sql.abstraction.util.StringUtils::quotize) - .collect(Collectors.toList()); - - final List primaryKeysOnClause = primaryKeys - .stream() - .map(pk -> String.format("dest.%s = src.%s", pk, pk)) - .collect(Collectors.toList()); - - merge.add("ON ( " + join(primaryKeysOnClause, " AND ") + " )"); - - merge.add("WHEN MATCHED THEN"); - merge.add("UPDATE"); - - final List columnsWithoutPKs = new ArrayList<>(columns); - columnsWithoutPKs.removeAll(primaryKeys); - - final String columnsToUpdate = columnsWithoutPKs - .stream() - .map(column -> String.format("%s = src.%s", column, column)) - .collect(Collectors.joining(", ")); - - merge.add("SET " + columnsToUpdate); - - merge.add("WHEN NOT MATCHED THEN"); - - final String insertColumns = columns.stream().map(column -> String.format("%s", column)).collect(Collectors.joining(", ")); - merge.add("INSERT (" + insertColumns + ")"); - - final String insertColumnValues = columns.stream().map(value -> String.format("src.%s", value)).collect(Collectors.joining(", ")); - merge.add("VALUES (" + insertColumnValues + ")"); - - return join(merge, " "); - } @Override diff --git a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/H2Engine.java b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/H2Engine.java index 0b5eaf93..9c149a04 100644 --- a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/H2Engine.java +++ b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/H2Engine.java @@ -463,6 +463,8 @@ protected MappedEntity createPreparedStatementForInserts(final DbEntity entity) insertIntoWithAutoInc.add("(" + join(columnsWithAutoInc, ", ") + ")"); insertIntoWithAutoInc.add("VALUES (" + join(valuesWithAutoInc, ", ") + ")"); + final String statementWithMerge = buildUpsertStatement(entity, columns, values); + final String statement = join(insertInto, " "); // The H2 DB doesn't implement INSERT RETURNING. Therefore, we just create a dummy statement, which will // never be invoked by this implementation. @@ -471,34 +473,23 @@ protected MappedEntity createPreparedStatementForInserts(final DbEntity entity) logger.trace(statement); - PreparedStatement ps = null, psReturn = null, psWithAutoInc = null, psMerge; + final PreparedStatement ps, psReturn, psWithAutoInc, psMerge; try { // Generate keys when the table has at least 1 column with auto generate value. - final int generateKeys = columnWithAutoIncName != null ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS; + final int generateKeys = columnWithAutoIncName != null? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS; ps = this.conn.prepareStatement(statement, generateKeys); psReturn = this.conn.prepareStatement(insertReturnStatement, generateKeys); psWithAutoInc = this.conn.prepareStatement(statementWithAutoInt, generateKeys); - - final String statementWithMerge = buildUpsertStatement(entity, columns, values); psMerge = this.conn.prepareStatement(statementWithMerge, generateKeys); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - // The auto incremented column must be set, so when persisting a row, it's possible to retrieve its value - // by consulting the column name from this MappedEntity. - .setAutoIncColumn(columnWithAutoIncName) - .setUpsert(psMerge); - - } catch (final IllegalArgumentException e) { - logger.trace("Returning entity without an UPSERT/MERGE prepared statement."); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc); - + .setInsert(ps) + .setInsertReturning(psReturn) + .setInsertWithAutoInc(psWithAutoInc) + // The auto incremented column must be set, so when persisting a row, it's possible to retrieve its value + // by consulting the column name from this MappedEntity. + .setAutoIncColumn(columnWithAutoIncName) + .setUpsert(psMerge); } catch (final SQLException ex) { throw new DatabaseEngineException("Something went wrong handling statement", ex); } diff --git a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/MySqlEngine.java b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/MySqlEngine.java index 0f88e310..5b712802 100644 --- a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/MySqlEngine.java +++ b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/MySqlEngine.java @@ -48,7 +48,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; import static com.feedzai.commons.sql.abstraction.util.StringUtils.md5; import static com.feedzai.commons.sql.abstraction.util.StringUtils.quotize; @@ -392,72 +391,18 @@ protected MappedEntity createPreparedStatementForInserts(final DbEntity entity) logger.trace(statement); logger.trace(statementWithAutoInt); - PreparedStatement ps = null, psReturn = null, psWithAutoInc = null, psUpsert; + final PreparedStatement ps, psReturn, psWithAutoInc; try { ps = conn.prepareStatement(statement, Statement.RETURN_GENERATED_KEYS); psReturn = conn.prepareStatement(insertReturnStatement); psWithAutoInc = conn.prepareStatement(statementWithAutoInt); - final String upsertStatement = buildUpsertStatement(entity, columns, values); - psUpsert = conn.prepareStatement(upsertStatement); - - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - .setUpsert(psUpsert); - - } catch (final IllegalArgumentException e) { - logger.trace("Returning entity without an UPSERT/MERGE prepared statement."); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc); - + return new MappedEntity().setInsert(ps).setInsertReturning(psReturn).setInsertWithAutoInc(psWithAutoInc); } catch (final SQLException ex) { throw new DatabaseEngineException("Something went wrong handling statement", ex); } } - /** - * Helper method to create an insert statement on duplicate key conflict for this engine. - * - * @param entity The entity. - * @param columns The columns of this entity. - * @param values The values of the entity. - * - * @return An insert statement on duplicate key conflict. - */ - private String buildUpsertStatement(final DbEntity entity, final List columns, final List values) { - - if (entity.getPkFields().isEmpty() || columns.isEmpty() || values.isEmpty()) { - throw new IllegalArgumentException("The MERGE command was not created because the entity has no primary keys. Skipping statement creation."); - } - - List insertIntoIgnoring = new ArrayList<>(); - insertIntoIgnoring.add("INSERT INTO"); - insertIntoIgnoring.add(quotize(entity.getName(), escapeCharacter())); - - insertIntoIgnoring.add("(" + join(columns, ", ") + ")"); - insertIntoIgnoring.add("VALUES (" + join(values, ", ") + ")"); - - final List primaryKeys = entity.getPkFields().stream().map(pk -> quotize(pk, escapeCharacter())).collect(Collectors.toList()); - insertIntoIgnoring.add("ON DUPLICATE KEY UPDATE"); - - final List columnsWithoutPKs = new ArrayList<>(columns); - columnsWithoutPKs.removeAll(primaryKeys); - - final String columnsToUpdate = columnsWithoutPKs - .stream() - .map(column -> String.format("%s = VALUES(%s)", column, column)) - .collect(Collectors.joining(", ")); - - insertIntoIgnoring.add(columnsToUpdate); - - return join(insertIntoIgnoring, " "); - - } - @Override protected void dropSequences(DbEntity entity) throws DatabaseEngineException { /* diff --git a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/PostgreSqlEngine.java b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/PostgreSqlEngine.java index 20fdb439..5170ba62 100644 --- a/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/PostgreSqlEngine.java +++ b/src/main/java/com/feedzai/commons/sql/abstraction/engine/impl/PostgreSqlEngine.java @@ -35,7 +35,6 @@ import com.feedzai.commons.sql.abstraction.engine.handler.OperationFault; import com.feedzai.commons.sql.abstraction.engine.handler.QueryExceptionHandler; import com.feedzai.commons.sql.abstraction.engine.impl.postgresql.PostgresSqlQueryExceptionHandler; -import com.feedzai.commons.sql.abstraction.util.StringUtils; import java.util.stream.Collectors; import org.postgresql.Driver; @@ -407,53 +406,39 @@ protected MappedEntity createPreparedStatementForInserts(final DbEntity entity) final String insertStatement = join(insertInto, " "); final String insertReturnStatement = join(insertIntoReturn, " "); final String statementWithAutoInt = join(insertIntoWithAutoInc, " "); + final String upsert = buildUpsertStatement(entity, columns, values); logger.trace(insertStatement); logger.trace(insertReturnStatement); + logger.trace(upsert); - PreparedStatement ps = null, psReturn = null, psWithAutoInc = null, psUpsert; + PreparedStatement ps, psReturn, psWithAutoInc, psUpsert; try { ps = conn.prepareStatement(insertStatement); psReturn = conn.prepareStatement(insertReturnStatement); psWithAutoInc = conn.prepareStatement(statementWithAutoInt); - - final String upsert = buildUpsertStatement(entity, columns, values); psUpsert = conn.prepareStatement(upsert); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - .setUpsert(psUpsert) - .setAutoIncColumn(returning); - - } catch (final IllegalArgumentException e) { - logger.trace("Returning entity without an UPSERT/MERGE prepared statement."); - return new MappedEntity() - .setInsert(ps) - .setInsertReturning(psReturn) - .setInsertWithAutoInc(psWithAutoInc) - .setAutoIncColumn(returning); - + return new MappedEntity().setInsert(ps).setInsertReturning(psReturn).setInsertWithAutoInc(psWithAutoInc).setUpsert(psUpsert).setAutoIncColumn(returning); } catch (final SQLException ex) { throw new DatabaseEngineException("Something went wrong handling statement", ex); } } /** - * Helper method to create an insert statement on duplicate key conflict for this engine. + * Helper method to create a insert on conflict statement that updates for this engine. * * @param entity The entity. * @param columns The columns of this entity. * @param values The values of the entity. * - * @return An insert statement on duplicate key conflict. + * @return A insert on conflict statement. */ private String buildUpsertStatement(final DbEntity entity, final List columns, final List values) { if (entity.getPkFields().isEmpty() || columns.isEmpty() || values.isEmpty()) { - throw new IllegalArgumentException("The MERGE command was not created because the entity has no primary keys. Skipping statement creation."); + return ""; } List insertIntoIgnoring = new ArrayList<>(); @@ -463,7 +448,7 @@ private String buildUpsertStatement(final DbEntity entity, final List co insertIntoIgnoring.add("(" + join(columns, ", ") + ")"); insertIntoIgnoring.add("VALUES (" + join(values, ", ") + ")"); - final List primaryKeys = entity.getPkFields().stream().map(StringUtils::quotize).collect(Collectors.toList()); + final List primaryKeys = entity.getPkFields().stream().map(pk -> quotize(pk)).collect(Collectors.toList()); insertIntoIgnoring.add("ON CONFLICT (" + join(primaryKeys, ", ") + ")"); insertIntoIgnoring.add("DO UPDATE"); diff --git a/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/BatchUpdateTest.java b/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/BatchUpdateTest.java index a8704a77..f751fee0 100644 --- a/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/BatchUpdateTest.java +++ b/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/BatchUpdateTest.java @@ -827,7 +827,7 @@ public void batchInsertDuplicateFlushWithDBErrorTest() throws Exception { */ @Test public void batchInsertOnIgnoreDuplicateFlushTest() throws Exception { - assumeTrue(ImmutableList.of("postresql", "mysql", "cockroach", "db2").contains(dbConfig.vendor) && dbConfig.vendor.startsWith("h2")); + assumeTrue(ImmutableList.of("h2", "postresql").contains(dbConfig.vendor)); final TestBatchListener batchListener = new TestBatchListener(); final int numTestEntries = 2; diff --git a/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/EngineCloseTest.java b/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/EngineCloseTest.java index a349adaa..407fd2ce 100644 --- a/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/EngineCloseTest.java +++ b/src/test/java/com/feedzai/commons/sql/abstraction/engine/impl/abs/EngineCloseTest.java @@ -123,7 +123,7 @@ public void setUp() throws DatabaseFactoryException { @Test public void closingAnEngineShouldFlushAndCloseInsertPSsAndCloseCachedPSs(@Capturing final Statement preparedStatementMock) throws DatabaseEngineException, SQLException, NameAlreadyExistsException { - assumeTrue(!ImmutableList.of("postgresql", "cockroach", "mysql", "db2").contains(config.vendor) && !config.vendor.startsWith("h2")); + assumeTrue(!ImmutableList.of("postgresql", "cockroach").contains(config.vendor) && !config.vendor.startsWith("h2")); engine.addEntity(buildEntity("ENTITY-1")); engine.addEntity(buildEntity("ENTITY-2"));