From bb7df713912783c1e5de33d8996cdb0cdea40a68 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Sat, 10 Aug 2019 21:40:23 +1200 Subject: [PATCH] #1782 - ENH: Add truncate(Class... types) and truncate(String... tables) --- src/main/java/io/ebean/DB.java | 14 +++++ src/main/java/io/ebean/Database.java | 11 ++++ .../config/dbplatform/DatabasePlatform.java | 25 +++++++++ .../config/dbplatform/db2/DB2Platform.java | 1 + .../config/dbplatform/h2/H2Platform.java | 3 +- .../config/dbplatform/hana/HanaPlatform.java | 1 - .../dbplatform/hsqldb/HsqldbPlatform.java | 2 +- .../dbplatform/mysql/MySqlPlatform.java | 3 +- .../dbplatform/oracle/OraclePlatform.java | 2 +- .../dbplatform/postgres/PostgresPlatform.java | 2 +- .../dbplatform/sqlite/SQLitePlatform.java | 2 +- .../server/core/DefaultServer.java | 34 ++++++++++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 53 +++---------------- src/test/java/main/StartCockroach.java | 16 ++++++ .../java/org/tests/model/nofk/Test2NoFk.java | 6 +-- .../org/tests/model/nofk/TestTruncate.java | 46 ++++++++++++++++ src/test/resources/logback-test.xml | 2 +- 17 files changed, 167 insertions(+), 56 deletions(-) create mode 100644 src/test/java/main/StartCockroach.java create mode 100644 src/test/java/org/tests/model/nofk/TestTruncate.java diff --git a/src/main/java/io/ebean/DB.java b/src/main/java/io/ebean/DB.java index db386a5e5..0379bae6d 100644 --- a/src/main/java/io/ebean/DB.java +++ b/src/main/java/io/ebean/DB.java @@ -1237,4 +1237,18 @@ public class DB { return getDefault().json(); } + /** + * Truncate the base tables for the given bean types. + */ + public static void truncate(Class... types) { + getDefault().truncate(types); + } + + /** + * Truncate the given tables. + */ + public static void truncate(String... tables) { + getDefault().truncate(tables); + } + } diff --git a/src/main/java/io/ebean/Database.java b/src/main/java/io/ebean/Database.java index 8a7b3a9f4..ee9ece29d 100644 --- a/src/main/java/io/ebean/Database.java +++ b/src/main/java/io/ebean/Database.java @@ -1580,4 +1580,15 @@ public interface Database { *

*/ Set validateQuery(Query query); + + /** + * Truncate all the given tables. + */ + void truncate(String... tables); + + /** + * Truncate the base tables for the given bean types. + */ + void truncate(Class... tables); + } diff --git a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java index f5f865dfc..8f5e4b41c 100644 --- a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java @@ -139,6 +139,10 @@ public class DatabasePlatform { */ protected Platform platform = Platform.GENERIC; + protected String truncateTable = "truncate table %s"; + protected String truncateTableBefore; + protected String truncateTableAfter; + protected String columnAliasPrefix = "c"; protected String tableAliasPlaceHolder = "${ta}"; @@ -708,6 +712,27 @@ public class DatabasePlatform { return persistBatchOnCascade; } + /** + * Return a statement to truncate a table. + */ + public String truncateStatement(String table) { + return String.format(truncateTable, table); + } + + /** + * Return a statement to execute prior to truncate (like disable foreign keys for mysql). + */ + public String truncateStatementBefore() { + return truncateTableBefore; + } + + /** + * Return a statement to execute after truncate (like enable foreign keys for mysql). + */ + public String truncateStatementAfter() { + return truncateTableAfter; + } + /** * Create the DB schema if it does not exist. */ diff --git a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java index b8ba7d227..df76c6064 100644 --- a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java +++ b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java @@ -22,6 +22,7 @@ public class DB2Platform extends DatabasePlatform { this.platform = Platform.DB2; this.maxTableNameLength = 18; this.maxConstraintNameLength = 18; + this.truncateTable = "truncate table %s reuse storage ignore delete triggers immediate"; this.sqlLimiter = new Db2SqlLimiter(); this.dbIdentity.setSupportsGetGeneratedKeys(true); diff --git a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java index d3418904d..c914559eb 100644 --- a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java +++ b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java @@ -26,7 +26,8 @@ public class H2Platform extends DatabasePlatform { this.supportsDeleteTableAlias = true; this.dbDefaultValue.setNow("now()"); this.columnAliasPrefix = null; - + //this.truncateTableBefore = "set referential_integrity false"; + //this.truncateTableAfter = "set referential_integrity true"; this.exceptionTranslator = new SqlErrorCodes() .addAcquireLock("50200","HYT00") diff --git a/src/main/java/io/ebean/config/dbplatform/hana/HanaPlatform.java b/src/main/java/io/ebean/config/dbplatform/hana/HanaPlatform.java index 3e06d0094..6422f7c81 100644 --- a/src/main/java/io/ebean/config/dbplatform/hana/HanaPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/hana/HanaPlatform.java @@ -18,7 +18,6 @@ public class HanaPlatform extends DatabasePlatform { this.persistBatchOnCascade = PersistBatch.NONE; this.supportsResultSetConcurrencyModeUpdatable = false; this.columnAliasPrefix = null; - this.historySupport = new HanaHistorySupport(); this.basicSqlLimiter = new HanaBasicSqlLimiter(); diff --git a/src/main/java/io/ebean/config/dbplatform/hsqldb/HsqldbPlatform.java b/src/main/java/io/ebean/config/dbplatform/hsqldb/HsqldbPlatform.java index 1450b01f7..b8cb1e0dd 100644 --- a/src/main/java/io/ebean/config/dbplatform/hsqldb/HsqldbPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/hsqldb/HsqldbPlatform.java @@ -21,7 +21,7 @@ public class HsqldbPlatform extends DatabasePlatform { super(); this.platform = Platform.HSQLDB; this.dbEncrypt = new H2DbEncrypt(); - + this.truncateTable = "delete from %s"; this.dbIdentity.setIdType(IdType.IDENTITY); this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsSequence(true); diff --git a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java index e744ecbad..977ea97f0 100644 --- a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java @@ -31,7 +31,8 @@ public class MySqlPlatform extends DatabasePlatform { this.dbEncrypt = new MySqlDbEncrypt(); this.historySupport = new MySqlHistorySupport(); this.columnAliasPrefix = null; - + //this.truncateTableBefore = "set foreign_key_checks = 0"; + //this.truncateTableAfter = "set foreign_key_checks = 1"; this.dbIdentity.setIdType(IdType.IDENTITY); this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsIdentity(true); diff --git a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java index cedd2a481..5ba237428 100644 --- a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java @@ -30,7 +30,7 @@ public class OraclePlatform extends DatabasePlatform { this.sqlLimiter = new RownumSqlLimiter(); this.basicSqlLimiter = new BasicSqlAnsiLimiter(); this.historySupport = new OracleDbHistorySupport(); - + this.truncateTable = "truncate table %s cascade"; dbIdentity.setIdType(IdType.SEQUENCE); dbIdentity.setSupportsSequence(true); dbIdentity.setSupportsIdentity(true); diff --git a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java index 938f5b3b3..f7a4f63d9 100644 --- a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java @@ -36,7 +36,7 @@ public class PostgresPlatform extends DatabasePlatform { this.clobDbType = Types.VARCHAR; this.nativeUuidType = true; this.columnAliasPrefix = null; - + this.truncateTable = "truncate table %s cascade"; this.dbEncrypt = new PostgresDbEncrypt(); this.historySupport = new PostgresHistorySupport(); diff --git a/src/main/java/io/ebean/config/dbplatform/sqlite/SQLitePlatform.java b/src/main/java/io/ebean/config/dbplatform/sqlite/SQLitePlatform.java index 2e15fd007..09db19312 100644 --- a/src/main/java/io/ebean/config/dbplatform/sqlite/SQLitePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/sqlite/SQLitePlatform.java @@ -17,7 +17,7 @@ public class SQLitePlatform extends DatabasePlatform { this.dbIdentity.setSupportsGetGeneratedKeys(false); this.dbIdentity.setSupportsSequence(false); this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()"); - + this.truncateTable = "delete from %s"; this.booleanDbType = Types.INTEGER; this.likeClauseRaw = "like ?"; this.likeClauseEscaped = "like ?"; diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 85c4391db..9818052c3 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -124,8 +124,10 @@ import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.time.Clock; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -639,6 +641,38 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { externalModification(evt); } + @Override + public void truncate(Class... types) { + List tableNames = new ArrayList<>(); + for (Class type : types) { + tableNames.add(getBeanDescriptor(type).getBaseTable()); + } + truncate(tableNames.toArray(new String[0])); + } + + @Override + public void truncate(String... tables) { + try (Connection connection = getDataSource().getConnection()) { + executeSql(connection, databasePlatform.truncateStatementBefore()); + for (String table : tables) { + executeSql(connection, databasePlatform.truncateStatement(table)); + } + executeSql(connection, databasePlatform.truncateStatementAfter()); + connection.commit(); + } catch(SQLException e) { + throw new PersistenceException("Error executing truncate", e); + } + } + + private void executeSql(Connection connection, String sql) throws SQLException { + if (sql != null) { + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + transactionManager.log().sql().debug(sql); + stmt.execute(); + } + } + } + /** * Clear the query execution statistics. */ diff --git a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java index bde5e8b34..e52dda337 100644 --- a/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java +++ b/src/test/java/io/ebeaninternal/api/TDSpiEbeanServer.java @@ -94,7 +94,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void setClock(Clock clock) { - } @Override @@ -104,7 +103,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void shutdownManaged() { - } @Override @@ -113,8 +111,15 @@ public class TDSpiEbeanServer implements SpiEbeanServer { } @Override - public void scopedTransactionEnter(TxScope txScope) { + public void truncate(String... tables) { + } + @Override + public void truncate(Class... tables) { + } + + @Override + public void scopedTransactionEnter(TxScope txScope) { } @Override @@ -229,7 +234,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void clearServerTransaction() { - } @Override @@ -249,7 +253,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void remoteTransactionEvent(RemoteTransactionEvent event) { - } @Override @@ -274,17 +277,14 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void merge(Object bean) { - } @Override public void merge(Object bean, MergeOptions options) { - } @Override public void merge(Object bean, MergeOptions options, Transaction transaction) { - } @Override @@ -304,12 +304,10 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void loadBean(LoadBeanRequest loadRequest) { - } @Override public void loadMany(LoadManyRequest loadRequest) { - } @Override @@ -324,27 +322,22 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void collectQueryStats(ObjectGraphNode objectGraphNode, long loadedBeanCount, long timeMicros) { - } @Override public void visitMetrics(MetricVisitor visitor) { - } @Override public void loadMany(BeanCollection collection, boolean onlyIds) { - } @Override public void loadBean(EntityBeanIntercept ebi) { - } @Override public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) { - } @Override @@ -439,7 +432,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void sort(List list, String sortByClause) { - } @Override @@ -449,12 +441,10 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void findDtoEach(SpiDtoQuery query, Consumer consumer) { - } @Override public void findDtoEachWhile(SpiDtoQuery query, Predicate consumer) { - } @Override @@ -509,7 +499,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void findEachRow(SpiSqlQuery query, RowConsumer consumer) { - } @Override @@ -539,7 +528,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void register(TransactionCallback transactionCallback) throws PersistenceException { - } @Override @@ -614,32 +602,26 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void flush() { - } @Override public void commitTransaction() { - } @Override public void rollbackTransaction() { - } @Override public void endTransaction() { - } @Override public void refresh(Object bean) { - } @Override public void refreshMany(Object bean, String propertyName) { - } @Override @@ -679,12 +661,10 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void findEach(Query query, Consumer consumer, Transaction transaction) { - } @Override public void findEachWhile(Query query, Predicate consumer, Transaction transaction) { - } @Override @@ -757,7 +737,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void save(Object bean) throws OptimisticLockException { - } @Override @@ -807,7 +786,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void externalModification(String tableName, boolean inserted, boolean updated, boolean deleted) { - } @Override @@ -817,37 +795,30 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void save(Object bean, Transaction transaction) throws OptimisticLockException { - } @Override public void markAsDirty(Object bean) { - } @Override public void update(Object bean) throws OptimisticLockException { - } @Override public void update(Object bean, Transaction t) throws OptimisticLockException { - } @Override public void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException { - } @Override public void insert(Object bean) { - } @Override public void insert(Object bean, Transaction t) { - } @Override @@ -887,7 +858,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) { - } @Override @@ -902,12 +872,10 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void execute(TxScope scope, Runnable r) { - } @Override public void execute(Runnable r) { - } @Override @@ -982,27 +950,22 @@ public class TDSpiEbeanServer implements SpiEbeanServer { @Override public void updateAll(Collection beans) throws OptimisticLockException { - } @Override public void updateAll(Collection beans, Transaction transaction) throws OptimisticLockException { - } @Override public void insertAll(Collection beans) { - } @Override public void insertAll(Collection beans, Transaction transaction) { - } @Override public void slowQueryCheck(long executionTimeMicros, int rowCount, SpiQuery query) { - } @Override diff --git a/src/test/java/main/StartCockroach.java b/src/test/java/main/StartCockroach.java new file mode 100644 index 000000000..5aa4a87a5 --- /dev/null +++ b/src/test/java/main/StartCockroach.java @@ -0,0 +1,16 @@ +package main; + +import io.ebean.docker.commands.CockroachConfig; +import io.ebean.docker.commands.CockroachContainer; + +public class StartCockroach { + + public static void main(String[] args) { + + CockroachConfig config = new CockroachConfig(); + config.setDbName("unit"); + + CockroachContainer container = new CockroachContainer(config); + container.startWithDropCreate(); + } +} diff --git a/src/test/java/org/tests/model/nofk/Test2NoFk.java b/src/test/java/org/tests/model/nofk/Test2NoFk.java index a6851bf9b..bf00656a8 100644 --- a/src/test/java/org/tests/model/nofk/Test2NoFk.java +++ b/src/test/java/org/tests/model/nofk/Test2NoFk.java @@ -1,6 +1,7 @@ package org.tests.model.nofk; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; @@ -21,9 +22,8 @@ public class Test2NoFk extends BaseTestCase { @Before public void setup() { // Reset t - Ebean.find(EFile2NoFk.class).delete(); - Ebean.find(EUserNoFk.class).delete(); - Ebean.find(EUserNoFkSoftDel.class).delete(); + DB.truncate(EFile2NoFk.class, EUserNoFk.class, EUserNoFkSoftDel.class); + assertThat(DB.find(EFile2NoFk.class).findCount()).isEqualTo(0); // There are two user accounts persisted in our database EUserNoFk root = new EUserNoFk(); diff --git a/src/test/java/org/tests/model/nofk/TestTruncate.java b/src/test/java/org/tests/model/nofk/TestTruncate.java new file mode 100644 index 000000000..bf22cb284 --- /dev/null +++ b/src/test/java/org/tests/model/nofk/TestTruncate.java @@ -0,0 +1,46 @@ +package org.tests.model.nofk; + +import io.ebean.DB; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestTruncate { + + @Test + public void test_truncateByClass() { + DB.truncate(EUserNoFkSoftDel.class, EUserNoFk.class); + + insertRows(); + assertRowCounts(1); + + DB.truncate(EUserNoFkSoftDel.class, EUserNoFk.class); + assertRowCounts(0); + } + + @Test + public void test_truncateByTable() { + DB.truncate(EUserNoFkSoftDel.class, EUserNoFk.class); + + insertRows(); + assertRowCounts(1); + + DB.truncate("euser_no_fk_soft_del", "euser_no_fk"); + assertRowCounts(0); + } + + private void insertRows() { + EUserNoFk root = new EUserNoFk(); + root.setUserName("root"); + DB.save(root); + + EUserNoFkSoftDel rootSoftDel = new EUserNoFkSoftDel(); + rootSoftDel.setUserName("root"); + DB.save(rootSoftDel); + } + + private void assertRowCounts(int expected) { + assertThat(DB.find(EUserNoFk.class).findCount()).isEqualTo(expected); + assertThat(DB.find(EUserNoFkSoftDel.class).findCount()).isEqualTo(expected); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index 8d4ee5d7c..50294ed9b 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -79,7 +79,7 @@ - +