From d26203db1fddf3dd711ed932babcab4278e24cd5 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 3 Oct 2016 20:52:53 +1300 Subject: [PATCH] #824 - Refactor com.avaje.ebean.config.dbplatform DbType & DbPlatformType - support custom DbType mapping options --- .../ebean/config/CustomDbTypeMapping.java | 54 +++++++++++++++ .../avaje/ebean/config/DbMigrationConfig.java | 9 ++- .../DbPlatformName.java => Platform.java} | 4 +- .../com/avaje/ebean/config/ServerConfig.java | 55 ++++++++++++++- .../config/dbplatform/DatabasePlatform.java | 15 +++++ .../config/dbplatform/DbPlatformType.java | 32 +++++++++ .../dbplatform/DbPlatformTypeParser.java | 40 +++++++++++ .../avaje/ebean/dbmigration/DbMigration.java | 8 +-- .../avaje/ebean/dbmigration/DbOffline.java | 6 +- .../ebean/config/PropertiesWrapperTest.java | 7 +- .../dbplatform/DatabasePlatformTest.java | 46 +++++++++++++ .../dbplatform/DbPlatformTypeParserTest.java | 67 +++++++++++++++++++ 12 files changed, 323 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/config/CustomDbTypeMapping.java rename src/main/java/com/avaje/ebean/config/{dbplatform/DbPlatformName.java => Platform.java} (84%) create mode 100644 src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParser.java create mode 100644 src/test/java/com/avaje/ebean/config/dbplatform/DatabasePlatformTest.java create mode 100644 src/test/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParserTest.java diff --git a/src/main/java/com/avaje/ebean/config/CustomDbTypeMapping.java b/src/main/java/com/avaje/ebean/config/CustomDbTypeMapping.java new file mode 100644 index 000000000..ae6a7d244 --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/CustomDbTypeMapping.java @@ -0,0 +1,54 @@ +package com.avaje.ebean.config; + +import com.avaje.ebean.config.dbplatform.DbType; + +/** + * Custom mappings for DB types that override the default. + * + * @see ServerConfig#addCustomMapping(DbType, String) + */ +public class CustomDbTypeMapping { + + protected final DbType type; + + protected final String columnDefinition; + + protected final Platform platform; + + /** + * Create a mapping. + */ + public CustomDbTypeMapping(DbType type, String columnDefinition, Platform platform) { + this.type = type; + this.columnDefinition = columnDefinition; + this.platform = platform; + } + + /** + * Create a mapping that should apply to all the database platforms. + */ + public CustomDbTypeMapping(DbType type, String columnDefinition) { + this(type, columnDefinition, null); + } + + /** + * Return the DB type the mapping applies to. + */ + public DbType getType() { + return type; + } + + /** + * Return the DB column definition to use. + */ + public String getColumnDefinition() { + return columnDefinition; + } + + /** + * Return the platform this mapping should apply to. Null means it applied to all platforms. + */ + public Platform getPlatform() { + return platform; + } +} diff --git a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java index ebb4d4889..5e4155289 100644 --- a/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java +++ b/src/main/java/com/avaje/ebean/config/DbMigrationConfig.java @@ -1,7 +1,6 @@ package com.avaje.ebean.config; import com.avaje.ebean.EbeanServer; -import com.avaje.ebean.config.dbplatform.DbPlatformName; import com.avaje.ebean.dbmigration.DbMigration; import org.avaje.dbmigration.MigrationConfig; import org.avaje.dbmigration.MigrationRunner; @@ -20,7 +19,7 @@ public class DbMigrationConfig { /** * The database platform to generate migration DDL for. */ - protected DbPlatformName platform; + protected Platform platform; /** * Set to true if the DB migration should be generated on server start. @@ -112,14 +111,14 @@ public class DbMigrationConfig { * We typically need to explicitly specify this as migration can often be generated * when running against H2. */ - public DbPlatformName getPlatform() { + public Platform getPlatform() { return platform; } /** * Set the DB platform to generate migration DDL for. */ - public void setPlatform(DbPlatformName platform) { + public void setPlatform(Platform platform) { this.platform = platform; } @@ -366,7 +365,7 @@ public class DbMigrationConfig { includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment); generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop); - platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform); + platform = properties.getEnum(Platform.class, "migration.platform", platform); generate = properties.getBoolean("migration.generate", generate); version = properties.get("migration.version", version); diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformName.java b/src/main/java/com/avaje/ebean/config/Platform.java similarity index 84% rename from src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformName.java rename to src/main/java/com/avaje/ebean/config/Platform.java index 357e89b21..bba8d43e2 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformName.java +++ b/src/main/java/com/avaje/ebean/config/Platform.java @@ -1,9 +1,9 @@ -package com.avaje.ebean.config.dbplatform; +package com.avaje.ebean.config; /** * Built in supported platforms. */ -public enum DbPlatformName { +public enum Platform { /** * Generic base platform configured via properties or code. diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 9dd0d05bb..70cf6dbcf 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -7,6 +7,7 @@ import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.cache.ServerCachePlugin; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.config.dbplatform.DbEncrypt; +import com.avaje.ebean.config.dbplatform.DbType; import com.avaje.ebean.event.BeanFindController; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; @@ -83,6 +84,8 @@ public class ServerConfig { private ContainerConfig containerConfig; + private List customDbTypeMappings = new ArrayList(); + /** * The underlying properties that were used during configuration. */ @@ -1437,7 +1440,7 @@ public class ServerConfig { *

* You can also set this in ebean.proprerties: *

- * + *

*

{@code
    * # set via ebean.properties
    * ebean.encryptKeyManager=com.avaje.tests.basic.encrypt.BasicEncyptKeyManager
@@ -1770,7 +1773,7 @@ public class ServerConfig {
    * queries that normally hit L2 bean cache automatically will not do so after a write/persist
    * on the transaction.
    * 

- * + *

*

{@code
    *
    *   // assume Customer has L2 bean caching enabled ...
@@ -1932,6 +1935,54 @@ public class ServerConfig {
     this.resourceDirectory = resourceDirectory;
   }
 
+  /**
+   * Add a custom type mapping.
+   * 

+ *

{@code
+   *
+   *   // set the default mapping for BigDecimal.class/decimal
+   *   serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
+   *
+   *   // set the default mapping for String.class/varchar but only for Postgres
+   *   serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
+   *
+   * }
+ * + * @param type The DB type this mapping should apply to + * @param columnDefinition The column definition that should be used + * @param platform Optionally specify the platform this mapping should apply to. + */ + public void addCustomMapping(DbType type, String columnDefinition, Platform platform) { + customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform)); + } + + /** + * Add a custom type mapping that applies to all platforms. + *

+ *

{@code
+   *
+   *   // set the default mapping for BigDecimal/decimal
+   *   serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
+   *
+   *   // set the default mapping for String/varchar
+   *   serverConfig.addCustomMapping(DbType.VARCHAR, "text");
+   *
+   * }
+ * + * @param type The DB type this mapping should apply to + * @param columnDefinition The column definition that should be used + */ + public void addCustomMapping(DbType type, String columnDefinition) { + customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition)); + } + + /** + * Return the list of custom type mappings. + */ + public List getCustomTypeMappings() { + return customDbTypeMappings; + } + /** * Register a BeanQueryAdapter instance. *

diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java index 9416cc7ba..6289dd810 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java @@ -2,7 +2,9 @@ package com.avaje.ebean.config.dbplatform; import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.Query; +import com.avaje.ebean.config.CustomDbTypeMapping; import com.avaje.ebean.config.PersistBatch; +import com.avaje.ebean.config.Platform; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler; import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl; @@ -180,6 +182,19 @@ public class DatabasePlatform { */ public void configure(ServerConfig serverConfig) { dbTypeMap.config(nativeUuidType, serverConfig.getDbUuid()); + for (CustomDbTypeMapping mapping : serverConfig.getCustomTypeMappings()) { + if (platformMatch(mapping.getPlatform())) { + dbTypeMap.put(mapping.getType(), parse(mapping.getColumnDefinition())); + } + } + } + + private DbPlatformType parse(String columnDefinition) { + return DbPlatformType.parse(columnDefinition); + } + + private boolean platformMatch(Platform platform) { + return platform == null || platform.name().equalsIgnoreCase(name); } /** diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformType.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformType.java index 5ff8c2c6e..c2599d7aa 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformType.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformType.java @@ -28,6 +28,17 @@ public class DbPlatformType implements ExtraDbTypes { */ private final boolean canHaveLength; + /** + * Parse a type definition into a DbPlatformType. + *

+ * e.g. "decimal(18,6)" + * e.g. "text" + *

+ */ + public static DbPlatformType parse(String columnDefinition) { + return DbPlatformTypeParser.parse(columnDefinition); + } + /** * Construct with no length or scale. */ @@ -67,6 +78,27 @@ public class DbPlatformType implements ExtraDbTypes { this.canHaveLength = canHaveLength; } + /** + * Return the type name. + */ + public String getName() { + return name; + } + + /** + * Return the default length/precision. + */ + public int getDefaultLength() { + return defaultLength; + } + + /** + * Return the default scale. + */ + public int getDefaultScale() { + return defaultScale; + } + /** * Return the type for a specific property that incorporates the name, length, * precision and scale. diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParser.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParser.java new file mode 100644 index 000000000..14a1c60db --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParser.java @@ -0,0 +1,40 @@ +package com.avaje.ebean.config.dbplatform; + +/** + * Parse raw column definitions into DbPlatformType like "decimal(18,6)" and "varchar(20) and "json". + */ +class DbPlatformTypeParser { + + /** + * Parse the column definition and return the DbPlatformType. + */ + static DbPlatformType parse(String columnDefinition) { + + columnDefinition = columnDefinition.trim(); + + int openPos = columnDefinition.indexOf('('); + if (openPos == -1) { + return new DbPlatformType(columnDefinition); + } + int closePos = columnDefinition.indexOf(')', openPos); + if (closePos == -1) { + return new DbPlatformType(columnDefinition); + } + try { + int commaPos = columnDefinition.indexOf(',', openPos); + if (commaPos > -1) { + String type = columnDefinition.substring(0, openPos); + int scale = Integer.parseInt(columnDefinition.substring(openPos + 1, commaPos)); + int precision = Integer.parseInt(columnDefinition.substring(commaPos + 1, closePos)); + return new DbPlatformType(type, scale, precision); + + } else { + String type = columnDefinition.substring(0, openPos); + int scale = Integer.parseInt(columnDefinition.substring(openPos + 1, closePos)); + return new DbPlatformType(type, scale); + } + } catch (RuntimeException e) { + return new DbPlatformType(columnDefinition); + } + } +} diff --git a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java index 18fff98cb..06692d82c 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java +++ b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java @@ -7,7 +7,7 @@ import com.avaje.ebean.config.DbMigrationConfig; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DB2Platform; import com.avaje.ebean.config.dbplatform.DatabasePlatform; -import com.avaje.ebean.config.dbplatform.DbPlatformName; +import com.avaje.ebean.config.Platform; import com.avaje.ebean.config.dbplatform.H2Platform; import com.avaje.ebean.config.dbplatform.MsSqlServer2005Platform; import com.avaje.ebean.config.dbplatform.MySqlPlatform; @@ -139,7 +139,7 @@ public class DbMigration { * If not set this defaults to the platform of the default server. *

*/ - public void setPlatform(DbPlatformName platform) { + public void setPlatform(Platform platform) { setPlatform(getPlatform(platform)); } @@ -163,7 +163,7 @@ public class DbMigration { * from the migration (e.g. generate migration sql for MySql, Postgres and Oracle). *

*/ - public void addPlatform(DbPlatformName platform, String prefix) { + public void addPlatform(Platform platform, String prefix) { platforms.add(new Pair(getPlatform(platform), prefix)); } @@ -515,7 +515,7 @@ public class DbMigration { /** * Return the DatabasePlatform given the platform key. */ - protected DatabasePlatform getPlatform(DbPlatformName platform) { + protected DatabasePlatform getPlatform(Platform platform) { switch (platform) { case H2: return new H2Platform(); diff --git a/src/main/java/com/avaje/ebean/dbmigration/DbOffline.java b/src/main/java/com/avaje/ebean/dbmigration/DbOffline.java index f0f8c3783..fb25efb57 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/DbOffline.java +++ b/src/main/java/com/avaje/ebean/dbmigration/DbOffline.java @@ -1,6 +1,6 @@ package com.avaje.ebean.dbmigration; -import com.avaje.ebean.config.dbplatform.DbPlatformName; +import com.avaje.ebean.config.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,7 +19,7 @@ public class DbOffline { /** * Set the platform to use when creating the next EbeanServer instance. */ - public static void setPlatform(DbPlatformName dbPlatform) { + public static void setPlatform(Platform dbPlatform) { System.setProperty(KEY, dbPlatform.name()); } @@ -41,7 +41,7 @@ public class DbOffline { * Bring up the next EbeanServer instance using the H2 platform. */ public static void asH2() { - setPlatform(DbPlatformName.H2); + setPlatform(Platform.H2); } /** diff --git a/src/test/java/com/avaje/ebean/config/PropertiesWrapperTest.java b/src/test/java/com/avaje/ebean/config/PropertiesWrapperTest.java index 43f8606c9..6337548ac 100644 --- a/src/test/java/com/avaje/ebean/config/PropertiesWrapperTest.java +++ b/src/test/java/com/avaje/ebean/config/PropertiesWrapperTest.java @@ -1,6 +1,5 @@ package com.avaje.ebean.config; -import com.avaje.ebean.config.dbplatform.DbPlatformName; import org.junit.Test; import java.util.Properties; @@ -25,9 +24,9 @@ public class PropertiesWrapperTest { properties.put("platform","postgres"); PropertiesWrapper pw = new PropertiesWrapper("pref", "myserver", properties); - assertEquals(DbPlatformName.POSTGRES, pw.getEnum(DbPlatformName.class, "platform", DbPlatformName.H2)); - assertEquals(DbPlatformName.H2, pw.getEnum(DbPlatformName.class, "junk", DbPlatformName.H2)); - assertNull(pw.getEnum(DbPlatformName.class, "junk", null)); + assertEquals(Platform.POSTGRES, pw.getEnum(Platform.class, "platform", Platform.H2)); + assertEquals(Platform.H2, pw.getEnum(Platform.class, "junk", Platform.H2)); + assertNull(pw.getEnum(Platform.class, "junk", null)); } @Test diff --git a/src/test/java/com/avaje/ebean/config/dbplatform/DatabasePlatformTest.java b/src/test/java/com/avaje/ebean/config/dbplatform/DatabasePlatformTest.java new file mode 100644 index 000000000..923834382 --- /dev/null +++ b/src/test/java/com/avaje/ebean/config/dbplatform/DatabasePlatformTest.java @@ -0,0 +1,46 @@ +package com.avaje.ebean.config.dbplatform; + +import com.avaje.ebean.config.Platform; +import com.avaje.ebean.config.ServerConfig; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DatabasePlatformTest { + + @Test + public void defaultTypesForDecimalAndVarchar() throws Exception { + + DatabasePlatform dbPlatform = new DatabasePlatform(); + assertEquals(defaultDecimalDefn(dbPlatform), "decimal(38)"); + assertEquals(defaultDefn(DbType.VARCHAR, dbPlatform), "varchar(255)"); + } + + @Test + public void configure_customType() throws Exception { + + ServerConfig serverConfig = new ServerConfig(); + serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES); + serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(24,4)"); + + // PG renders custom decimal and varchar + PostgresPlatform pgPlatform = new PostgresPlatform(); + pgPlatform.configure(serverConfig); + assertEquals(defaultDecimalDefn(pgPlatform), "decimal(24,4)"); + assertEquals(defaultDefn(DbType.VARCHAR, pgPlatform), "text"); + + // H2 only renders custom decimal + H2Platform h2Platform = new H2Platform(); + h2Platform.configure(serverConfig); + assertEquals(defaultDecimalDefn(h2Platform), "decimal(24,4)"); + assertEquals(defaultDefn(DbType.VARCHAR, h2Platform), "varchar(255)"); + } + + private String defaultDecimalDefn(DatabasePlatform dbPlatform) { + return defaultDefn(DbType.DECIMAL, dbPlatform); + } + + private String defaultDefn(DbType type, DatabasePlatform dbPlatform) { + return dbPlatform.getDbTypeMap().get(type).renderType(0, 0); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParserTest.java b/src/test/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParserTest.java new file mode 100644 index 000000000..bc2990515 --- /dev/null +++ b/src/test/java/com/avaje/ebean/config/dbplatform/DbPlatformTypeParserTest.java @@ -0,0 +1,67 @@ +package com.avaje.ebean.config.dbplatform; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class DbPlatformTypeParserTest { + + @Test + public void parse_text() throws Exception { + + DbPlatformType type = DbPlatformTypeParser.parse("text"); + + assertEquals(type.getName(), "text"); + assertEquals(type.getDefaultLength(), 0); + assertEquals(type.getDefaultScale(), 0); + + assertEquals(type.renderType(0, 0), "text"); + assertEquals(type.renderType(40, 0), "text(40)"); + } + + @Test + public void parse_varchar20() throws Exception { + + DbPlatformType type = DbPlatformTypeParser.parse("varchar(20)"); + + assertEquals(type.getName(), "varchar"); + assertEquals(type.getDefaultLength(), 20); + assertEquals(type.getDefaultScale(), 0); + + assertEquals(type.renderType(0, 0), "varchar(20)"); + assertEquals(type.renderType(40, 0), "varchar(40)"); + } + + @Test + public void parse_decimal_18_6() throws Exception { + + DbPlatformType type = DbPlatformTypeParser.parse("decimal(18,6)"); + + assertEquals(type.getName(), "decimal"); + assertEquals(type.getDefaultLength(), 18); + assertEquals(type.getDefaultScale(), 6); + assertEquals(type.renderType(0, 0), "decimal(18,6)"); + } + + @Test + public void parse_something() throws Exception { + + DbPlatformType type = DbPlatformTypeParser.parse("something(asd,6)"); + + assertEquals(type.getName(), "something(asd,6)"); + assertEquals(type.getDefaultLength(), 0); + assertEquals(type.getDefaultScale(), 0); + assertEquals(type.renderType(0, 0), "something(asd,6)"); + } + + @Test + public void parse_invalid() throws Exception { + + DbPlatformType type = DbPlatformTypeParser.parse("something(asd"); + + assertEquals(type.getName(), "something(asd"); + assertEquals(type.getDefaultLength(), 0); + assertEquals(type.getDefaultScale(), 0); + assertEquals(type.renderType(0, 0), "something(asd"); + } +} \ No newline at end of file